Merge branch '1085-startup-wizard' into 'master'

Setup Wizard that asks for Doze Mode exception

Closes #1085 and #1018

See merge request !603
This commit is contained in:
akwizgran
2017-11-21 09:40:10 +00:00
49 changed files with 1105 additions and 518 deletions

View File

@@ -18,6 +18,7 @@ dependencies {
}
compile "com.android.support:cardview-v7:$supportVersion"
compile "com.android.support:support-annotations:$supportVersion"
compile 'com.android.support.constraint:constraint-layout:1.0.2'
compile('ch.acra:acra:4.8.5') {
exclude module: 'support-v4'
exclude module: 'support-annotations'
@@ -34,6 +35,7 @@ dependencies {
testCompile project(path: ':bramble-core', configuration: 'testOutput')
testCompile 'org.robolectric:robolectric:3.0'
testCompile 'org.robolectric:shadows-support-v4:3.0'
testCompile 'org.mockito:mockito-core:2.8.9'
}
@@ -55,6 +57,8 @@ dependencyVerification {
'com.android.support:support-vector-drawable:799bafe4c3de812386f0b291f744d5d6876452722dd40189b9ab87dbbf594ea1',
'com.android.support:recyclerview-v7:44040a888e23e0c93162a3377cfe06751080e3c22d369ab0d4301ef60d63b0fe',
'com.android.support:preference-v7:775101bd07bd052e455761c5c5d9523d7ad59f2f320e3e8cbde241fd6b1d6025',
'com.android.support.constraint:constraint-layout:b0c688cc2b7172608f8153a689d746da40f71e52d7e2fe2bfd9df2f92db77085',
'com.android.support.constraint:constraint-layout-solver:8c62525a9bc5cff5633a96cb9b32fffeccaf41b8841aa87fc22607070dea9b8d',
]
}

View File

@@ -15,6 +15,7 @@
<uses-permission android:name="android.permission.READ_LOGS"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<application
android:name="org.briarproject.briar.android.BriarApplicationImpl"

View File

@@ -27,8 +27,11 @@ import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
import org.briarproject.briar.android.keyagreement.IntroFragment;
import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
import org.briarproject.briar.android.keyagreement.ShowQrCodeFragment;
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.PasswordActivity;
import org.briarproject.briar.android.login.PasswordFragment;
import org.briarproject.briar.android.login.SetupActivity;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.android.panic.PanicPreferencesActivity;
@@ -148,6 +151,10 @@ public interface ActivityComponent {
void inject(RssFeedManageActivity activity);
// Fragments
void inject(AuthorNameFragment fragment);
void inject(PasswordFragment fragment);
void inject(DozeFragment fragment);
void inject(ContactListFragment fragment);
void inject(CreateGroupFragment fragment);

View File

@@ -12,11 +12,13 @@ import android.view.ViewGroup.LayoutParams;
import android.view.inputmethod.InputMethodManager;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.briar.R;
import org.briarproject.briar.android.AndroidComponent;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.DestroyableContext;
import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
import org.briarproject.briar.android.widget.TapSafeFrameLayout;
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
@@ -113,6 +115,22 @@ public abstract class BaseActivity extends AppCompatActivity
}
}
protected void showInitialFragment(BaseFragment f) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
.commit();
}
public void showNextFragment(BaseFragment f) {
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.step_next_in,
R.anim.step_previous_out, R.anim.step_previous_in,
R.anim.step_next_out)
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
.addToBackStack(f.getUniqueTag())
.commit();
}
private void showScreenFilterWarning() {
if (dialogFrag != null && dialogFrag.isVisible()) return;
Set<String> apps = screenFilterMonitor.getApps();

View File

@@ -14,7 +14,6 @@ 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.ResultHandler;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.login.PasswordActivity;
import org.briarproject.briar.android.panic.ExitActivity;
@@ -64,22 +63,6 @@ public abstract class BriarActivity extends BaseActivity {
}
}
protected void showInitialFragment(BaseFragment f) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
.commit();
}
public void showNextFragment(BaseFragment f) {
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.step_next_in,
R.anim.step_previous_out, R.anim.step_previous_in,
R.anim.step_next_out)
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
.addToBackStack(f.getUniqueTag())
.commit();
}
public void setSceneTransitionAnimation() {
if (Build.VERSION.SDK_INT < 21) return;
Transition slide = new Slide(Gravity.RIGHT);

View File

@@ -3,13 +3,13 @@ package org.briarproject.briar.android.activity;
public interface RequestCodes {
int REQUEST_PASSWORD = 1;
int REQUEST_BLUETOOTH = 2;
int REQUEST_INTRODUCTION = 3;
int REQUEST_GROUP_INVITE = 4;
int REQUEST_SHARE_FORUM = 5;
int REQUEST_WRITE_BLOG_POST = 6;
int REQUEST_SHARE_BLOG = 7;
int REQUEST_RINGTONE = 8;
int REQUEST_PERMISSION_CAMERA = 9;
int REQUEST_INTRODUCTION = 2;
int REQUEST_GROUP_INVITE = 3;
int REQUEST_SHARE_FORUM = 4;
int REQUEST_WRITE_BLOG_POST = 5;
int REQUEST_SHARE_BLOG = 6;
int REQUEST_RINGTONE = 7;
int REQUEST_PERMISSION_CAMERA = 8;
int REQUEST_DOZE_WHITELISTING = 9;
}

View File

@@ -0,0 +1,82 @@
package org.briarproject.briar.android.login;
import android.os.Bundle;
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.widget.Button;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
import static android.view.inputmethod.EditorInfo.IME_ACTION_NONE;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.briar.android.util.UiUtils.setError;
public class AuthorNameFragment extends SetupFragment {
private final static String TAG = AuthorNameFragment.class.getName();
private TextInputLayout authorNameWrapper;
private TextInputEditText authorNameInput;
private Button nextButton;
public static AuthorNameFragment newInstance() {
return new AuthorNameFragment();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getActivity().setTitle(getString(R.string.setup_title));
View v = inflater.inflate(R.layout.fragment_setup_author_name,
container, false);
authorNameWrapper =
(TextInputLayout) v.findViewById(R.id.nickname_entry_wrapper);
authorNameInput =
(TextInputEditText) v.findViewById(R.id.nickname_entry);
nextButton = (Button) v.findViewById(R.id.next);
authorNameInput.addTextChangedListener(this);
nextButton.setOnClickListener(this);
return v;
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
protected String getHelpText() {
return getString(R.string.setup_name_explanation);
}
@Override
public void onTextChanged(CharSequence authorName, int i, int i1, int i2) {
int authorNameLength = StringUtils.toUtf8(authorName.toString()).length;
boolean error = authorNameLength > MAX_AUTHOR_NAME_LENGTH;
setError(authorNameWrapper, getString(R.string.name_too_long), error);
boolean enabled = authorNameLength > 0 && !error;
authorNameInput
.setImeOptions(enabled ? IME_ACTION_NEXT : IME_ACTION_NONE);
authorNameInput.setOnEditorActionListener(enabled ? this : null);
nextButton.setEnabled(enabled);
}
@Override
public void onClick(View view) {
setupController.setAuthorName(authorNameInput.getText().toString());
}
}

View File

@@ -32,8 +32,6 @@ public class ChangePasswordActivity extends BaseActivity
@Inject
protected PasswordController passwordController;
@Inject
protected SetupController setupController;
private TextInputLayout currentPasswordEntryWrapper;
private TextInputLayout newPasswordEntryWrapper;
@@ -105,7 +103,7 @@ public class ChangePasswordActivity extends BaseActivity
String secondPassword = newPasswordConfirmation.getText().toString();
boolean passwordsMatch = firstPassword.equals(secondPassword);
float strength =
setupController.estimatePasswordStrength(firstPassword);
passwordController.estimatePasswordStrength(firstPassword);
strengthMeter.setStrength(strength);
UiUtils.setError(newPasswordEntryWrapper,
getString(R.string.password_too_weak),

View File

@@ -0,0 +1,94 @@
package org.briarproject.briar.android.login;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.util.UiUtils;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@TargetApi(23)
public class DozeFragment extends SetupFragment {
private final static String TAG = DozeFragment.class.getName();
private Button dozeButton;
private ProgressBar progressBar;
public static DozeFragment newInstance() {
return new DozeFragment();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getActivity().setTitle(getString(R.string.setup_doze_title));
View v = inflater.inflate(R.layout.fragment_setup_doze, container,
false);
dozeButton = (Button) v.findViewById(R.id.dozeButton);
progressBar = (ProgressBar) v.findViewById(R.id.progress);
dozeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
askForDozeWhitelisting();
}
});
return v;
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
protected String getHelpText() {
return getString(R.string.setup_doze_explanation);
}
@Override
public void onActivityResult(int request, int result, Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_DOZE_WHITELISTING) {
if (!setupController.needsDozeWhitelisting()) {
dozeButton.setEnabled(false);
onClick(dozeButton);
} else {
showOnboardingDialog(getContext(), getHelpText());
}
}
}
@SuppressLint("BatteryLife")
private void askForDozeWhitelisting() {
Intent i = UiUtils.getDozeWhitelistingIntent(getContext());
startActivityForResult(i, REQUEST_DOZE_WHITELISTING);
}
@Override
public void onClick(View view) {
dozeButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
setupController.createAccount();
}
}

View File

@@ -7,9 +7,12 @@ import org.briarproject.briar.android.controller.handler.ResultHandler;
@NotNullByDefault
public interface PasswordController extends ConfigController {
float estimatePasswordStrength(String password);
void validatePassword(String password,
ResultHandler<Boolean> resultHandler);
void changePassword(String password, String newPassword,
ResultHandler<Boolean> resultHandler);
}

View File

@@ -4,6 +4,7 @@ import android.content.SharedPreferences;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -27,14 +28,22 @@ public class PasswordControllerImpl extends ConfigControllerImpl
protected final Executor cryptoExecutor;
protected final CryptoComponent crypto;
private final PasswordStrengthEstimator strengthEstimator;
@Inject
PasswordControllerImpl(SharedPreferences briarPrefs,
DatabaseConfig databaseConfig,
@CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto) {
@CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto,
PasswordStrengthEstimator strengthEstimator) {
super(briarPrefs, databaseConfig);
this.cryptoExecutor = cryptoExecutor;
this.crypto = crypto;
this.strengthEstimator = strengthEstimator;
}
@Override
public float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
@Override
@@ -82,7 +91,7 @@ public class PasswordControllerImpl extends ConfigControllerImpl
return StringUtils.fromHexString(hex);
}
// Call inside cryptoExecutor
@CryptoExecutor
String encryptDatabaseKey(SecretKey key, String password) {
long now = System.currentTimeMillis();
byte[] encrypted = crypto.encryptWithPassword(key.getBytes(), password);

View File

@@ -0,0 +1,116 @@
package org.briarproject.briar.android.login;
import android.os.Bundle;
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.widget.Button;
import android.widget.ProgressBar;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.util.UiUtils;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
public class PasswordFragment extends SetupFragment {
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;
public static PasswordFragment newInstance() {
return new PasswordFragment();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getActivity().setTitle(getString(R.string.setup_password_intro));
View v = inflater.inflate(R.layout.fragment_setup_password, container,
false);
strengthMeter = (StrengthMeter) v.findViewById(R.id.strength_meter);
passwordEntryWrapper =
(TextInputLayout) v.findViewById(R.id.password_entry_wrapper);
passwordEntry = (TextInputEditText) v.findViewById(R.id.password_entry);
passwordConfirmationWrapper =
(TextInputLayout) v.findViewById(R.id.password_confirm_wrapper);
passwordConfirmation =
(TextInputEditText) v.findViewById(R.id.password_confirm);
nextButton = (Button) v.findViewById(R.id.next);
progressBar = (ProgressBar) v.findViewById(R.id.progress);
passwordEntry.addTextChangedListener(this);
passwordConfirmation.addTextChangedListener(this);
nextButton.setOnClickListener(this);
return v;
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
// the controller is not yet available in onCreateView()
if (!setupController.needsDozeWhitelisting()) {
nextButton.setText(R.string.create_account_button);
}
}
@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) {
if (!setupController.needsDozeWhitelisting()) {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
String password = passwordEntry.getText().toString();
setupController.setPassword(password);
setupController.showDozeOrCreateAccount();
}
}

View File

@@ -1,157 +1,64 @@
package org.briarproject.briar.android.login;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import org.briarproject.bramble.util.StringUtils;
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.handler.UiResultHandler;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.android.util.UiUtils;
import javax.inject.Inject;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.view.View.GONE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
public class SetupActivity extends BaseActivity implements OnClickListener,
OnEditorActionListener {
public class SetupActivity extends BaseActivity
implements BaseFragmentListener {
@Inject
SetupController setupController;
private TextInputLayout nicknameEntryWrapper;
private TextInputLayout passwordEntryWrapper;
private TextInputLayout passwordConfirmationWrapper;
private EditText nicknameEntry;
private EditText passwordEntry;
private EditText passwordConfirmation;
private StrengthMeter strengthMeter;
private Button createAccountButton;
private ProgressBar progress;
@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);
setContentView(R.layout.activity_setup);
setContentView(R.layout.activity_fragment_container);
nicknameEntryWrapper =
(TextInputLayout) findViewById(R.id.nickname_entry_wrapper);
passwordEntryWrapper =
(TextInputLayout) findViewById(R.id.password_entry_wrapper);
passwordConfirmationWrapper =
(TextInputLayout) findViewById(R.id.password_confirm_wrapper);
nicknameEntry = (EditText) findViewById(R.id.nickname_entry);
passwordEntry = (EditText) findViewById(R.id.password_entry);
passwordConfirmation = (EditText) findViewById(R.id.password_confirm);
strengthMeter = (StrengthMeter) findViewById(R.id.strength_meter);
createAccountButton = (Button) findViewById(R.id.create_account);
progress = (ProgressBar) findViewById(R.id.progress_wheel);
TextWatcher tw = 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) {
enableOrDisableContinueButton();
}
@Override
public void afterTextChanged(Editable s) {
}
};
nicknameEntry.addTextChangedListener(tw);
passwordEntry.addTextChangedListener(tw);
passwordConfirmation.addTextChangedListener(tw);
passwordConfirmation.setOnEditorActionListener(this);
createAccountButton.setOnClickListener(this);
if (state == null) {
showInitialFragment(AuthorNameFragment.newInstance());
}
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
setupController.setSetupActivity(this);
}
private void enableOrDisableContinueButton() {
if (progress == null) return; // Not created yet
if (passwordEntry.getText().length() > 0 && passwordEntry.hasFocus())
strengthMeter.setVisibility(VISIBLE);
else strengthMeter.setVisibility(GONE);
String nickname = nicknameEntry.getText().toString();
int nicknameLength = StringUtils.toUtf8(nickname).length;
String firstPassword = passwordEntry.getText().toString();
String secondPassword = passwordConfirmation.getText().toString();
boolean passwordsMatch = firstPassword.equals(secondPassword);
float strength =
setupController.estimatePasswordStrength(firstPassword);
strengthMeter.setStrength(strength);
UiUtils.setError(nicknameEntryWrapper,
getString(R.string.name_too_long),
nicknameLength > MAX_AUTHOR_NAME_LENGTH);
UiUtils.setError(passwordEntryWrapper,
getString(R.string.password_too_weak),
firstPassword.length() > 0 && strength < QUITE_WEAK);
UiUtils.setError(passwordConfirmationWrapper,
getString(R.string.passwords_do_not_match),
secondPassword.length() > 0 && !passwordsMatch);
createAccountButton.setEnabled(nicknameLength > 0
&& nicknameLength <= MAX_AUTHOR_NAME_LENGTH
&& passwordsMatch && strength >= QUITE_WEAK);
public void showPasswordFragment() {
showNextFragment(PasswordFragment.newInstance());
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
hideSoftKeyboard(v);
return true;
@TargetApi(23)
public void showDozeFragment() {
showNextFragment(DozeFragment.newInstance());
}
@Override
public void onClick(View view) {
// Replace the button with a progress bar
createAccountButton.setVisibility(INVISIBLE);
progress.setVisibility(VISIBLE);
String nickname = nicknameEntry.getText().toString();
String password = passwordEntry.getText().toString();
setupController.storeAuthorInfo(nickname, password,
new UiResultHandler<Void>(this) {
@Override
public void onResultUi(Void result) {
showMain();
}
});
}
private void showMain() {
public void showApp() {
Intent i = new Intent(this, NavDrawerActivity.class);
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
supportFinishAfterTransition();
overridePendingTransition(R.anim.screen_new_in, R.anim.screen_old_out);
}
@Override
@Deprecated
public void runOnDbThread(Runnable runnable) {
throw new RuntimeException("Don't use this deprecated method here.");
}
}

View File

@@ -6,9 +6,25 @@ import org.briarproject.briar.android.controller.handler.ResultHandler;
@NotNullByDefault
public interface SetupController {
void setSetupActivity(SetupActivity setupActivity);
boolean needsDozeWhitelisting();
void setAuthorName(String authorName);
void setPassword(String password);
float estimatePasswordStrength(String password);
void storeAuthorInfo(String nickname, String password,
ResultHandler<Void> resultHandler);
/**
* This should be called after the author name and the password have been
* set. It decides whether to ask for doze exception or create the account
* right away.
*/
void showDozeOrCreateAccount();
void createAccount();
void createAccount(final ResultHandler<Void> resultHandler);
}

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android.login;
import android.content.SharedPreferences;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
@@ -9,6 +10,8 @@ import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.controller.handler.ResultHandler;
import org.briarproject.briar.android.controller.handler.UiResultHandler;
import org.briarproject.briar.android.util.UiUtils;
import java.util.concurrent.Executor;
@@ -18,29 +21,75 @@ import javax.inject.Inject;
public class SetupControllerImpl extends PasswordControllerImpl
implements SetupController {
private final PasswordStrengthEstimator strengthEstimator;
@Nullable
private String authorName, password;
@Nullable
private SetupActivity setupActivity;
@Inject
SetupControllerImpl(SharedPreferences briarPrefs,
DatabaseConfig databaseConfig,
@CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto,
PasswordStrengthEstimator strengthEstimator) {
super(briarPrefs, databaseConfig, cryptoExecutor, crypto);
this.strengthEstimator = strengthEstimator;
super(briarPrefs, databaseConfig, cryptoExecutor, crypto,
strengthEstimator);
}
@Override
public float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
public void setSetupActivity(SetupActivity setupActivity) {
this.setupActivity = setupActivity;
}
@Override
public void storeAuthorInfo(final String nickname, final String password,
final ResultHandler<Void> resultHandler) {
public boolean needsDozeWhitelisting() {
if (setupActivity == null) throw new IllegalStateException();
return UiUtils.needsDozeWhitelisting(setupActivity);
}
@Override
public void setAuthorName(String authorName) {
this.authorName = authorName;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.showPasswordFragment();
}
@Override
public void setPassword(String password) {
this.password = password;
}
@Override
public void showDozeOrCreateAccount() {
if (setupActivity == null) throw new IllegalStateException();
if (needsDozeWhitelisting()) {
setupActivity.showDozeFragment();
} else {
createAccount();
}
}
@Override
public void createAccount() {
final UiResultHandler<Void> resultHandler =
new UiResultHandler<Void>(setupActivity) {
@Override
public void onResultUi(Void result) {
if (setupActivity == null)
throw new IllegalStateException();
setupActivity.showApp();
}
};
createAccount(resultHandler);
}
@Override
public void createAccount(final ResultHandler<Void> resultHandler) {
if (authorName == null || password == null)
throw new IllegalStateException();
cryptoExecutor.execute(new Runnable() {
@Override
public void run() {
databaseConfig.setLocalAuthorName(nickname);
databaseConfig.setLocalAuthorName(authorName);
SecretKey key = crypto.generateSecretKey();
databaseConfig.setEncryptionKey(key);
String hex = encryptDatabaseKey(key, password);

View File

@@ -0,0 +1,68 @@
package org.briarproject.briar.android.login;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View.OnClickListener;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import org.briarproject.briar.R;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
abstract class SetupFragment extends BaseFragment implements TextWatcher,
OnEditorActionListener, OnClickListener {
@Inject
SetupController setupController;
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.help_action, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_help) {
showOnboardingDialog(getContext(), getHelpText());
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
protected abstract String getHelpText();
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// noop
}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
// noop
}
@Override
public boolean onEditorAction(TextView textView, int actionId,
KeyEvent keyEvent) {
onClick(textView);
return true;
}
@Override
public void afterTextChanged(Editable editable) {
// noop
}
}

View File

@@ -1,14 +1,19 @@
package org.briarproject.briar.android.navdrawer;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.design.widget.NavigationView.OnNavigationItemSelectedListener;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.MenuItem;
@@ -49,9 +54,12 @@ import static android.support.v4.view.GravityCompat.START;
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO;
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.UPDATE;
import static org.briarproject.briar.android.util.UiUtils.getDaysUntilExpiry;
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
public class NavDrawerActivity extends BriarActivity implements
BaseFragmentListener, TransportStateListener,
@@ -134,6 +142,7 @@ public class NavDrawerActivity extends BriarActivity implements
}
@Override
@SuppressLint("NewApi")
public void onStart() {
super.onStart();
updateTransports();
@@ -143,6 +152,7 @@ public class NavDrawerActivity extends BriarActivity implements
if (expiry != NO) showExpiryWarning(expiry);
}
});
if (needsDozeWhitelisting(this)) requestDozeWhitelisting();
}
private void exitIfStartupFailed(Intent intent) {
@@ -178,15 +188,12 @@ public class NavDrawerActivity extends BriarActivity implements
}
@Override
public boolean onNavigationItemSelected(MenuItem item) {
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
drawerLayout.closeDrawer(START);
clearBackStack();
loadFragment(item.getItemId());
//Don't display the Settings Item as checked
if (item.getItemId() == R.id.nav_btn_settings) {
return false;
}
return true;
// Don't display the Settings item as checked
return item.getItemId() != R.id.nav_btn_settings;
}
@Override
@@ -204,7 +211,7 @@ public class NavDrawerActivity extends BriarActivity implements
getSupportFragmentManager()
.findFragmentByTag(ContactListFragment.TAG) == null) {
/*
* This Makes sure that the first fragment (ContactListFragment) the
* This makes sure that the first fragment (ContactListFragment) the
* user sees is the same as the last fragment the user sees before
* exiting. This models the typical Google navigation behaviour such
* as in Gmail/Inbox.
@@ -311,6 +318,24 @@ public class NavDrawerActivity extends BriarActivity implements
expiryWarning.setVisibility(VISIBLE);
}
@TargetApi(23)
private void requestDozeWhitelisting() {
new AlertDialog.Builder(this, R.style.BriarDialogTheme)
.setMessage(R.string.setup_doze_intro)
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
Intent i = getDozeWhitelistingIntent(
NavDrawerActivity.this);
startActivityForResult(i,
REQUEST_DOZE_WHITELISTING);
}
})
.show();
}
private void initializeTransports(final LayoutInflater inflater) {
transports = new ArrayList<>(3);

View File

@@ -1,14 +1,18 @@
package org.briarproject.briar.android.util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.PowerManager;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.text.Spannable;
@@ -23,14 +27,16 @@ import android.view.View;
import android.widget.TextView;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.R;
import org.briarproject.briar.android.view.ArticleMovementMethod;
import org.briarproject.briar.android.widget.LinkDialogFragment;
import javax.annotation.Nullable;
import static android.content.Intent.*;
import static android.content.Context.POWER_SERVICE;
import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE;
@@ -38,7 +44,7 @@ import static android.text.format.DateUtils.FORMAT_ABBREV_TIME;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static org.briarproject.briar.BuildConfig.*;
import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
public class UiUtils {
@@ -149,4 +155,36 @@ public class UiUtils {
}
};
}
public static void showOnboardingDialog(Context ctx, String text) {
new AlertDialog.Builder(ctx, R.style.OnboardingDialogTheme)
.setMessage(text)
.setNeutralButton(R.string.got_it,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
dialog.cancel();
}
})
.show();
}
public static boolean needsDozeWhitelisting(Context ctx) {
if (Build.VERSION.SDK_INT < 23) return false;
PowerManager pm = (PowerManager) ctx.getSystemService(POWER_SERVICE);
String packageName = ctx.getPackageName();
if (pm == null) throw new AssertionError();
return !pm.isIgnoringBatteryOptimizations(packageName);
}
@TargetApi(23)
@SuppressLint("BatteryLife")
public static Intent getDozeWhitelistingIntent(Context ctx) {
Intent i = new Intent();
i.setAction(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
i.setData(Uri.parse("package:" + ctx.getPackageName()));
return i;
}
}

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/margin_activity_vertical"
android:paddingEnd="@dimen/margin_activity_horizontal"
android:paddingLeft="@dimen/margin_activity_horizontal"
android:paddingRight="@dimen/margin_activity_horizontal"
android:paddingStart="@dimen/margin_activity_horizontal"
android:paddingTop="@dimen/margin_activity_vertical">
<android.support.design.widget.TextInputLayout
android:id="@+id/nickname_entry_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_medium"
app:errorEnabled="true"
app:hintEnabled="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="parent">
<android.support.design.widget.TextInputEditText
android:id="@+id/nickname_entry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/choose_nickname"
android:imeOptions="actionNext"
android:inputType="text|textCapWords"
android:maxLines="1"/>
<requestFocus/>
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/next"
style="@style/BriarButton.Default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_activity_horizontal"
android:enabled="false"
android:text="@string/setup_next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/nickname_entry_wrapper"
app:layout_constraintVertical_bias="1.0"
tools:enabled="true"/>
</android.support.constraint.ConstraintLayout>
</ScrollView>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/margin_activity_vertical"
android:paddingEnd="@dimen/margin_activity_horizontal"
android:paddingLeft="@dimen/margin_activity_horizontal"
android:paddingRight="@dimen/margin_activity_horizontal"
android:paddingStart="@dimen/margin_activity_horizontal"
android:paddingTop="@dimen/margin_activity_vertical">
<TextView
android:id="@+id/setup_explanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_doze_intro"
android:textSize="@dimen/text_size_medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<Button
android:id="@+id/dozeButton"
style="@style/BriarButton.Default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_activity_horizontal"
android:text="@string/setup_doze_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/setup_explanation"/>
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/dozeButton"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/dozeButton"/>
</android.support.constraint.ConstraintLayout>
</ScrollView>

View File

@@ -5,12 +5,11 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".android.login.SetupActivity">
android:fillViewport="true">
<RelativeLayout
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="@dimen/margin_activity_vertical"
android:paddingEnd="@dimen/margin_activity_horizontal"
android:paddingLeft="@dimen/margin_activity_horizontal"
@@ -18,38 +17,16 @@
android:paddingStart="@dimen/margin_activity_horizontal"
android:paddingTop="@dimen/margin_activity_vertical">
<TextView
android:id="@+id/setup_explanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_explanation"/>
<android.support.design.widget.TextInputLayout
android:id="@+id/nickname_entry_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/setup_explanation"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/margin_medium"
app:errorEnabled="true">
<android.support.design.widget.TextInputEditText
android:id="@+id/nickname_entry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/choose_nickname"
android:imeOptions="actionNext"
android:inputType="text|textCapWords"
android:maxLines="1"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/password_entry_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/nickname_entry_wrapper"
android:layout_centerHorizontal="true"
app:errorEnabled="true">
android:layout_marginTop="8dp"
app:errorEnabled="true"
app:hintEnabled="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<android.support.design.widget.TextInputEditText
android:id="@+id/password_entry"
@@ -58,55 +35,70 @@
android:hint="@string/choose_password"
android:imeOptions="actionNext"
android:inputType="textPassword"
android:maxLines="1"/>
android:maxLines="1">
<requestFocus/>
</android.support.design.widget.TextInputEditText>
</android.support.design.widget.TextInputLayout>
<org.briarproject.briar.android.login.StrengthMeter
android:id="@+id/strength_meter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/password_entry_wrapper"
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/margin_medium"
android:visibility="gone"/>
android:layout_marginTop="8dp"
android:visibility="invisible"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password_entry_wrapper"
tools:visibility="visible"/>
<android.support.design.widget.TextInputLayout
android:id="@+id/password_confirm_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/strength_meter"
android:layout_centerHorizontal="true"
app:errorEnabled="true">
android:layout_marginTop="8dp"
app:errorEnabled="true"
app:hintEnabled="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/strength_meter">
<android.support.design.widget.TextInputEditText
android:id="@+id/password_confirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/confirm_password"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:maxLines="1"/>
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/create_account"
android:id="@+id/next"
style="@style/BriarButton.Default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/password_confirm_wrapper"
android:layout_centerHorizontal="true"
android:enabled="false"
android:text="@string/create_account_button"/>
android:text="@string/setup_next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password_confirm_wrapper"
app:layout_constraintVertical_bias="1.0"
tools:enabled="true"/>
<ProgressBar
android:id="@+id/progress_wheel"
android:id="@+id/progress"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/create_account"
android:layout_centerHorizontal="true"
android:visibility="invisible"/>
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password_confirm_wrapper"
app:layout_constraintVertical_bias="1.0"/>
</RelativeLayout>
</android.support.constraint.ConstraintLayout>
</ScrollView>
</ScrollView>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_help"
android:icon="@drawable/ic_info_white"
android:title="@string/more_info"
app:showAsAction="always"/>
</menu>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Briar регистрация</string>
<string name="setup_explanation">Briar профилът се съхранява криптиран във вашето устройство, а не в облака. Ако деинсталирате Briar или забравите паролата си, няма как да възстановите профила и данните си.</string>
<string name="setup_password_explanation">Briar профилът се съхранява криптиран във вашето устройство, а не в облака. Ако деинсталирате Briar или забравите паролата си, няма как да възстановите профила и данните си.</string>
<string name="choose_nickname">Изберете име</string>
<string name="choose_password">Изберете парола</string>
<string name="confirm_password">Потвърдете парола</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Configuració de Briar</string>
<string name="setup_explanation">El teu compte de Briar està encriptat al teu dispositiu, no al núvol. Si desinstal·les Briar o oblides la contrasenya, no hi ha manera de recuperar el teu compte i les teves dades.</string>
<string name="setup_password_explanation">El teu compte de Briar està encriptat al teu dispositiu, no al núvol. Si desinstal·les Briar o oblides la contrasenya, no hi ha manera de recuperar el teu compte i les teves dades.</string>
<string name="choose_nickname">Trieu el sobrenom</string>
<string name="choose_password">Trieu la contrasenya</string>
<string name="confirm_password">Confirmeu la contrasenya</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Briar einrichten</string>
<string name="setup_explanation">Dein Briar-Konto wird verschlüsselt auf deinem Gerät gespeichert und nicht mit der \"Cloud\" synchronisiert. Wenn du Briar deinstallierst oder dein Passwort vergisst, können das Konto und deine Daten nicht wiederhergestellt werden.</string>
<string name="setup_password_explanation">Dein Briar-Konto wird verschlüsselt auf deinem Gerät gespeichert und nicht mit der \"Cloud\" synchronisiert. Wenn du Briar deinstallierst oder dein Passwort vergisst, können das Konto und deine Daten nicht wiederhergestellt werden.</string>
<string name="choose_nickname">Wähle deinen Benutzernamen</string>
<string name="choose_password">Wähle dein Passwort</string>
<string name="confirm_password">Passwort bestätigen</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Configuración de Briar</string>
<string name="setup_explanation">Tu cuenta de Briar se almacena de manera cifrada en tu dispositivo y no en ninguna nube. Si desinstalas Briar u olvidas tu contraseña, no podrás recuperar ni tu cuenta ni tus datos.</string>
<string name="setup_password_explanation">Tu cuenta de Briar se almacena de manera cifrada en tu dispositivo y no en ninguna nube. Si desinstalas Briar u olvidas tu contraseña, no podrás recuperar ni tu cuenta ni tus datos.</string>
<string name="choose_nickname">Elige tu nombre de usuario</string>
<string name="choose_password">Elige tu contraseña</string>
<string name="confirm_password">Confirma tu contraseña</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Briar ezarpena</string>
<string name="setup_explanation">Zure Briar kontua zure gailuan zifratuta gordetzen da, ez hodeian. Briar desinstalatzen baduzu edo pasahitza ahazten baduzu, ez dago zure kontua eta datuak berreskuratzeko modurik.</string>
<string name="setup_password_explanation">Zure Briar kontua zure gailuan zifratuta gordetzen da, ez hodeian. Briar desinstalatzen baduzu edo pasahitza ahazten baduzu, ez dago zure kontua eta datuak berreskuratzeko modurik.</string>
<string name="choose_nickname">Hautatu zure ezizena</string>
<string name="choose_password">Hautatu zure pasahitza</string>
<string name="confirm_password">Berretsi zure pasahitza</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Briarin asetus</string>
<string name="setup_explanation">Sinun Briar tili on tallennettu salattuna sinun laitteelle, ei pilvipalvelimelle. Jos poistat Briarin asennuksen tai unohdat salasanasi, ei ole mitään keinoa jolla tilisi tai dataasi voisi palauttaa.</string>
<string name="setup_password_explanation">Sinun Briar tili on tallennettu salattuna sinun laitteelle, ei pilvipalvelimelle. Jos poistat Briarin asennuksen tai unohdat salasanasi, ei ole mitään keinoa jolla tilisi tai dataasi voisi palauttaa.</string>
<string name="choose_nickname">Valitse nimimerkki</string>
<string name="choose_password">Valitse salasana</string>
<string name="confirm_password">Vahvista salasana</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Configuration de Briar</string>
<string name="setup_explanation">Votre compte Briar est enregistré chiffré sur votre appareil et non sur le nuage. Si vous désinstallez Briar ou oubliez votre mot de passe, votre compte et vos données sont irrécupérables.</string>
<string name="setup_password_explanation">Votre compte Briar est enregistré chiffré sur votre appareil et non sur le nuage. Si vous désinstallez Briar ou oubliez votre mot de passe, votre compte et vos données sont irrécupérables.</string>
<string name="choose_nickname">Choisir votre pseudonyme</string>
<string name="choose_password">Choisir votre mot de passe</string>
<string name="confirm_password">Confirmer votre mot de passe</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Configuración de Briar</string>
<string name="setup_explanation">Briar almacena a súa configuración encriptada no dispositivo, non na nube. Se desinstala Briar ou esquece a súa clave, non hai forma de recuperar a súa conta e datos.</string>
<string name="setup_password_explanation">Briar almacena a súa configuración encriptada no dispositivo, non na nube. Se desinstala Briar ou esquece a súa clave, non hai forma de recuperar a súa conta e datos.</string>
<string name="choose_nickname">Escolle o teu alcume</string>
<string name="choose_password">Escolle a túa clave</string>
<string name="confirm_password">Confirma a túa clave</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">ब्रियर सेटअप</string>
<string name="setup_explanation">आपका ब्रियर खाता आपके डिवाइस पर एन्क्रिप्ट किया गया है, न कि क्लाउड में। यदि आप बिअर की स्थापना रद्द करते हैं या अपना पासवर्ड भूल जाते हैं, तो आपके खाता और आपके डेटा को पुनर्प्राप्त करने का कोई तरीका नहीं है।</string>
<string name="setup_password_explanation">आपका ब्रियर खाता आपके डिवाइस पर एन्क्रिप्ट किया गया है, न कि क्लाउड में। यदि आप बिअर की स्थापना रद्द करते हैं या अपना पासवर्ड भूल जाते हैं, तो आपके खाता और आपके डेटा को पुनर्प्राप्त करने का कोई तरीका नहीं है।</string>
<string name="choose_nickname">आपका मुंहबोला नाम चुनें</string>
<string name="choose_password">अपना पासवर्ड चुनें</string>
<string name="confirm_password">अपने पासवर्ड की पुष्टि करें</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Impostazione Briar</string>
<string name="setup_explanation">Il tuo account Briar si trova criptato sul tuo dispositivo e non nel cloud. Se disinstalli Briar o dimentichi la tua password, non c\'è modo di recuperare il tuo account o i tuoi dati.</string>
<string name="setup_password_explanation">Il tuo account Briar si trova criptato sul tuo dispositivo e non nel cloud. Se disinstalli Briar o dimentichi la tua password, non c\'è modo di recuperare il tuo account o i tuoi dati.</string>
<string name="choose_nickname">Scegli il tuo nickname</string>
<string name="choose_password">Scegli la tua password</string>
<string name="confirm_password">Conferma la tua password</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Oppsett av Briar</string>
<string name="setup_explanation">Din Briar-konto er lagret kryptert på din enhet, ikke i skyen. Hvis du avinstallerer Briar eller glemmer passordet ditt, går det ikke an å gjenopprette kontoen og dataen din.</string>
<string name="setup_password_explanation">Din Briar-konto er lagret kryptert på din enhet, ikke i skyen. Hvis du avinstallerer Briar eller glemmer passordet ditt, går det ikke an å gjenopprette kontoen og dataen din.</string>
<string name="choose_nickname">Velg kallenavn</string>
<string name="choose_password">Velg passord</string>
<string name="confirm_password">Bekreft passord</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Configuracion de Briar</string>
<string name="setup_explanation">Vòstre compte Briar es salvat e chifrat sus vòstre aparelh, non pas sus un servidor alonhat \"cloud\". Se desinstalletz Briar o oblidetz vòstre senhal, vòstre compte e vòstras donadas serà pas possible de los recuperar.</string>
<string name="setup_password_explanation">Vòstre compte Briar es salvat e chifrat sus vòstre aparelh, non pas sus un servidor alonhat \"cloud\". Se desinstalletz Briar o oblidetz vòstre senhal, vòstre compte e vòstras donadas serà pas possible de los recuperar.</string>
<string name="choose_nickname">Causir un escais-nom</string>
<string name="choose_password">Causir un senhal</string>
<string name="confirm_password">Confirmar lo senhal</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Configuração de Briar</string>
<string name="setup_explanation">Sua conta Briar é armazenada criptografada apenas no seu dispositivo, não na nuvem. Se você Desinstalar o Briar ou esquecer sua senha, não será possível recuperar sua conta e seus dados.</string>
<string name="setup_password_explanation">Sua conta Briar é armazenada criptografada apenas no seu dispositivo, não na nuvem. Se você Desinstalar o Briar ou esquecer sua senha, não será possível recuperar sua conta e seus dados.</string>
<string name="choose_nickname">Escolha seu apelido</string>
<string name="choose_password">Escolha sua senha</string>
<string name="confirm_password">Confirme sua senha</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Настройка Briar</string>
<string name="setup_explanation">Ваша учетная запись Briar хранится в зашифрованном виде только на устройстве. Если вы удалите Briar или забудете пароль, то не сможете восстановить свою учетную запись и данные.</string>
<string name="setup_password_explanation">Ваша учетная запись Briar хранится в зашифрованном виде только на устройстве. Если вы удалите Briar или забудете пароль, то не сможете восстановить свою учетную запись и данные.</string>
<string name="choose_nickname">Выберите псевдоним</string>
<string name="choose_password">Выберите пароль</string>
<string name="confirm_password">Подтвердите пароль</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Rregullimi i Briar-it</string>
<string name="setup_explanation">Llogaria juaj Briar depozitohet e fshehtëzuar në pajisjen tuaj, jo në re. Nëse e çinstaloni Briar-in ose harroni fjalëkalimin tuaj, nuk ka rrugë për ta rimarrë llogarinë tuaj dhe të dhënat tuaja.</string>
<string name="setup_password_explanation">Llogaria juaj Briar depozitohet e fshehtëzuar në pajisjen tuaj, jo në re. Nëse e çinstaloni Briar-in ose harroni fjalëkalimin tuaj, nuk ka rrugë për ta rimarrë llogarinë tuaj dhe të dhënat tuaja.</string>
<string name="choose_nickname">Zgjidhni nofkën tuaj</string>
<string name="choose_password">Zgjidhni fjalëkalimin tuaj</string>
<string name="confirm_password">Ripohoni fjalëkalimin tuaj</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Briar Podešavanje</string>
<string name="setup_explanation">Vaš Briar račun je kriptovan i sačuvan na vašem uređaju, ne u server-oblaku. Ako deinstalirate Briar ili zaboravite šifru, nema načina da povratite vaš račun ili vaše podatke.</string>
<string name="setup_password_explanation">Vaš Briar račun je kriptovan i sačuvan na vašem uređaju, ne u server-oblaku. Ako deinstalirate Briar ili zaboravite šifru, nema načina da povratite vaš račun ili vaše podatke.</string>
<string name="choose_nickname">Izaberite vaš nadimak</string>
<string name="choose_password">Izaberite vašu šifru</string>
<string name="confirm_password">Potvrdite vašu šifru</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">Briar Kurulum</string>
<string name="setup_explanation">Briar hesabınız bulutta değil, cihazınızda şifreli olarak saklanır. Briar\'ı kaldırırsanız veya şifrenizi unutursanız, hesabınızı ve verilerinizi kurtarmanın bir yolu yoktur.</string>
<string name="setup_password_explanation">Briar hesabınız bulutta değil, cihazınızda şifreli olarak saklanır. Briar\'ı kaldırırsanız veya şifrenizi unutursanız, hesabınızı ve verilerinizi kurtarmanın bir yolu yoktur.</string>
<string name="choose_nickname">Kullanıcı adınızı belirleyin</string>
<string name="choose_password">Parolanızı belirleyin</string>
<string name="confirm_password">Parolanızı doğrulayın</string>

View File

@@ -2,7 +2,7 @@
<resources>
<!--Setup-->
<string name="setup_title">安装 Briar</string>
<string name="setup_explanation">您的 Briar 账号只会被加密储存在您的设备上,不会被上传。如果您卸载了 Briar 或忘记了密码,将无法恢复您的账号和数据。</string>
<string name="setup_password_explanation">您的 Briar 账号只会被加密储存在您的设备上,不会被上传。如果您卸载了 Briar 或忘记了密码,将无法恢复您的账号和数据。</string>
<string name="choose_nickname">设置昵称</string>
<string name="choose_password">设置密码</string>
<string name="confirm_password">确认密码</string>

View File

@@ -2,8 +2,15 @@
<resources>
<!-- Setup -->
<string name="setup_title">Briar Setup</string>
<string name="setup_explanation">Your Briar account is stored encrypted on your device, not in the cloud. If you uninstall Briar or forget your password, there\'s no way to recover your account and your data.</string>
<string name="setup_title">Welcome to Briar</string>
<string name="setup_name_explanation">Your nickname will be shown next to any content you post. You can\'t change it after creating your account.</string>
<string name="setup_next">Next</string>
<string name="setup_password_intro">Choose a Password</string>
<string name="setup_password_explanation">Your Briar account is stored encrypted on your device, not in the cloud. If you forget your password or uninstall Briar, there\'s no way to recover your account.\n\nChoose a long password that\'s hard to guess, such as four random words, or ten random letters, numbers and symbols.</string>
<string name="setup_doze_title">Background Connections</string>
<string name="setup_doze_intro">To receive messages, Briar needs to stay connected in the background.</string>
<string name="setup_doze_explanation">To receive messages, Briar needs to stay connected in the background. Please disable battery optimizations so Briar can stay connected.</string>
<string name="setup_doze_button">Allow Connections</string>
<string name="choose_nickname">Choose your nickname</string>
<string name="choose_password">Choose your password</string>
<string name="confirm_password">Confirm your password</string>
@@ -11,6 +18,7 @@
<string name="password_too_weak">Password is too weak</string>
<string name="passwords_do_not_match">Passwords do not match</string>
<string name="create_account_button">Create Account</string>
<string name="more_info">More Information</string>
<!-- Login -->
<string name="enter_password">Enter your password:</string>

View File

@@ -1,7 +1,5 @@
package org.briarproject.briar.android.login;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.design.widget.TextInputLayout;
import android.widget.Button;
import android.widget.EditText;
@@ -29,12 +27,8 @@ import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUIT
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.STRONG;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.WEAK;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -45,8 +39,6 @@ import static org.mockito.Mockito.when;
packageName = "org.briarproject.briar")
public class ChangePasswordActivityTest {
private static final int TIMEOUT_MS = 10 * 1000;
private TestChangePasswordActivity changePasswordActivity;
private TextInputLayout passwordConfirmationWrapper;
private EditText currentPassword;
@@ -57,8 +49,6 @@ public class ChangePasswordActivityTest {
@Mock
private PasswordController passwordController;
@Mock
private SetupController setupController;
@Captor
private ArgumentCaptor<ResultHandler<Boolean>> resultCaptor;
@@ -110,12 +100,9 @@ public class ChangePasswordActivityTest {
@Test
public void testChangePasswordUI() {
PasswordController mockedPasswordController = this.passwordController;
SetupController mockedSetupController = this.setupController;
changePasswordActivity.setPasswordController(mockedPasswordController);
changePasswordActivity.setSetupController(mockedSetupController);
changePasswordActivity.setPasswordController(passwordController);
// Mock strong password strength answer
when(mockedSetupController.estimatePasswordStrength(anyString()))
when(passwordController.estimatePasswordStrength(anyString()))
.thenReturn(STRONG);
String curPass = "old.password";
String safePass = "really.safe.password";
@@ -127,7 +114,7 @@ public class ChangePasswordActivityTest {
changePasswordButton.performClick();
// Verify that the controller's method was called with the correct
// params and get the callback
verify(mockedPasswordController, times(1))
verify(passwordController, times(1))
.changePassword(eq(curPass), eq(safePass),
resultCaptor.capture());
// execute the callbacks
@@ -135,89 +122,39 @@ public class ChangePasswordActivityTest {
assertEquals(changePasswordActivity.isFinishing(), true);
}
@Test
public void testPasswordChange() {
PasswordController passwordController =
changePasswordActivity.getPasswordController();
SetupController setupController =
changePasswordActivity.getSetupController();
// mock a resulthandler
ResultHandler<Void> resultHandler =
(ResultHandler<Void>) mock(ResultHandler.class);
setupController.storeAuthorInfo("nick", "some.old.pass", resultHandler);
// blocking verification call with timeout that waits until the mocked
// result gets called with handle 0L, the expected value
verify(resultHandler, timeout(TIMEOUT_MS).times(1)).onResult(null);
SharedPreferences prefs = changePasswordActivity
.getSharedPreferences("db", Context.MODE_PRIVATE);
// Confirm database key
assertTrue(prefs.contains("key"));
String oldKey = prefs.getString("key", null);
// mock a resulthandler
ResultHandler<Boolean> resultHandler2 =
(ResultHandler<Boolean>) mock(ResultHandler.class);
passwordController.changePassword("some.old.pass", "some.strong.pass",
resultHandler2);
// blocking verification call with timeout that waits until the mocked
// result gets called with handle 0L, the expected value
verify(resultHandler2, timeout(TIMEOUT_MS).times(1)).onResult(true);
// Confirm database key
assertTrue(prefs.contains("key"));
assertNotEquals(oldKey, prefs.getString("key", null));
// Note that Robolectric uses its own persistant storage that it
// wipes clean after each test run, no need to clean up manually.
}
@Test
public void testStrengthMeter() {
SetupController controller =
changePasswordActivity.getSetupController();
String strongPass = "very.strong.password.123";
String weakPass = "we";
String quiteStrongPass = "quite.strong";
float val = controller.estimatePasswordStrength(strongPass);
assertTrue(val == STRONG);
val = controller.estimatePasswordStrength(weakPass);
assertTrue(val < WEAK && val > NONE);
val = controller.estimatePasswordStrength(quiteStrongPass);
assertTrue(val < STRONG && val > QUITE_WEAK);
}
@Test
public void testStrengthMeterUI() {
Assert.assertNotNull(changePasswordActivity);
// replace the setup controller with our mocked copy
SetupController mockedController = this.setupController;
changePasswordActivity.setSetupController(mockedController);
// replace the password controller with our mocked copy
changePasswordActivity.setPasswordController(passwordController);
// Mock answers for UI testing only
when(mockedController.estimatePasswordStrength("strong")).thenReturn(
when(passwordController.estimatePasswordStrength("strong")).thenReturn(
STRONG);
when(mockedController.estimatePasswordStrength("qstrong")).thenReturn(
when(passwordController.estimatePasswordStrength("qstrong")).thenReturn(
QUITE_STRONG);
when(mockedController.estimatePasswordStrength("qweak")).thenReturn(
when(passwordController.estimatePasswordStrength("qweak")).thenReturn(
QUITE_WEAK);
when(mockedController.estimatePasswordStrength("weak")).thenReturn(
when(passwordController.estimatePasswordStrength("weak")).thenReturn(
WEAK);
when(mockedController.estimatePasswordStrength("empty")).thenReturn(
when(passwordController.estimatePasswordStrength("empty")).thenReturn(
NONE);
// Test the meters progress and color for several values
testStrengthMeter("strong", STRONG, StrengthMeter.GREEN);
Mockito.verify(mockedController, Mockito.times(1))
Mockito.verify(passwordController, Mockito.times(1))
.estimatePasswordStrength(eq("strong"));
testStrengthMeter("qstrong", QUITE_STRONG, StrengthMeter.LIME);
Mockito.verify(mockedController, Mockito.times(1))
Mockito.verify(passwordController, Mockito.times(1))
.estimatePasswordStrength(eq("qstrong"));
testStrengthMeter("qweak", QUITE_WEAK, StrengthMeter.YELLOW);
Mockito.verify(mockedController, Mockito.times(1))
Mockito.verify(passwordController, Mockito.times(1))
.estimatePasswordStrength(eq("qweak"));
testStrengthMeter("weak", WEAK, StrengthMeter.ORANGE);
Mockito.verify(mockedController, Mockito.times(1))
Mockito.verify(passwordController, Mockito.times(1))
.estimatePasswordStrength(eq("weak"));
// Not sure this should be the correct behaviour on an empty input ?
testStrengthMeter("empty", NONE, StrengthMeter.RED);
Mockito.verify(mockedController, Mockito.times(1))
Mockito.verify(passwordController, Mockito.times(1))
.estimatePasswordStrength(eq("empty"));
}
}

View File

@@ -0,0 +1,99 @@
package org.briarproject.briar.android.login;
import android.content.SharedPreferences;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.briar.android.controller.handler.ResultHandler;
import org.jmock.Expectations;
import org.junit.Test;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
public class PasswordControllerImplTest extends BrambleMockTestCase {
private final SharedPreferences briarPrefs =
context.mock(SharedPreferences.class);
private final DatabaseConfig databaseConfig =
context.mock(DatabaseConfig.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final PasswordStrengthEstimator estimator =
context.mock(PasswordStrengthEstimator.class);
private final SharedPreferences.Editor editor =
context.mock(SharedPreferences.Editor.class);
private final Executor cryptoExecutor = new ImmediateExecutor();
private final String oldPassword = "some.old.pass";
private final String newPassword = "some.new.pass";
private final String oldEncryptedHex = "010203";
private final String newEncryptedHex = "020304";
private final byte[] oldEncryptedBytes = new byte[] {1, 2, 3};
private final byte[] newEncryptedBytes = new byte[] {2, 3, 4};
private final byte[] keyBytes = getSecretKey().getBytes();
@Test
public void testChangePasswordReturnsTrue() {
context.checking(new Expectations() {{
// Look up the encrypted DB key
oneOf(briarPrefs).getString("key", null);
will(returnValue(oldEncryptedHex));
// Decrypt and re-encrypt the key
oneOf(crypto).decryptWithPassword(oldEncryptedBytes, oldPassword);
will(returnValue(keyBytes));
oneOf(crypto).encryptWithPassword(keyBytes, newPassword);
will(returnValue(newEncryptedBytes));
// Store the re-encrypted key
oneOf(briarPrefs).edit();
will(returnValue(editor));
oneOf(editor).putString("key", newEncryptedHex);
oneOf(editor).apply();
}});
PasswordControllerImpl p = new PasswordControllerImpl(briarPrefs,
databaseConfig, cryptoExecutor, crypto, estimator);
final AtomicBoolean capturedResult = new AtomicBoolean(false);
p.changePassword(oldPassword, newPassword,
new ResultHandler<Boolean>() {
@Override
public void onResult(Boolean result) {
capturedResult.set(result);
}
});
assertTrue(capturedResult.get());
}
@Test
public void testChangePasswordReturnsFalseIfOldPasswordIsWrong() {
context.checking(new Expectations() {{
// Look up the encrypted DB key
oneOf(briarPrefs).getString("key", null);
will(returnValue(oldEncryptedHex));
// Try to decrypt the key - the password is wrong
oneOf(crypto).decryptWithPassword(oldEncryptedBytes, oldPassword);
will(returnValue(null));
}});
PasswordControllerImpl p = new PasswordControllerImpl(briarPrefs,
databaseConfig, cryptoExecutor, crypto, estimator);
final AtomicBoolean capturedResult = new AtomicBoolean(true);
p.changePassword(oldPassword, newPassword,
new ResultHandler<Boolean>() {
@Override
public void onResult(Boolean result) {
capturedResult.set(result);
}
});
assertFalse(capturedResult.get());
}
}

View File

@@ -0,0 +1,117 @@
package org.briarproject.briar.android.login;
import android.support.design.widget.TextInputLayout;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.R;
import org.briarproject.briar.android.TestBriarApplication;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;
import static junit.framework.Assert.assertEquals;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.NONE;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_STRONG;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.STRONG;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.WEAK;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.shadows.support.v4.SupportFragmentTestUtil.startFragment;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21,
application = TestBriarApplication.class,
packageName = "org.briarproject.briar")
public class PasswordFragmentTest {
private PasswordFragment passwordFragment = new PasswordFragment();
private EditText passwordEntry;
private EditText passwordConfirmation;
private TextInputLayout passwordConfirmationWrapper;
private StrengthMeter strengthMeter;
private Button createAccountButton;
@Mock
private SetupController setupController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
startFragment(passwordFragment, SetupActivity.class);
View v = passwordFragment.getView();
passwordEntry = (EditText) v.findViewById(R.id.password_entry);
passwordConfirmation = (EditText) v.findViewById(R.id.password_confirm);
passwordConfirmationWrapper =
(TextInputLayout) v.findViewById(R.id.password_confirm_wrapper);
strengthMeter = (StrengthMeter) v.findViewById(R.id.strength_meter);
createAccountButton = (Button) v.findViewById(R.id.next);
}
@Test
public void testCreateAccountUI() {
String safePass = "really.safe.password";
passwordFragment.setupController = setupController;
when(setupController.needsDozeWhitelisting()).thenReturn(false);
when(setupController.estimatePasswordStrength(safePass))
.thenReturn(STRONG);
passwordEntry.setText(safePass);
passwordConfirmation.setText(safePass);
// Confirm that the create account button is clickable
assertEquals(createAccountButton.isEnabled(), true);
createAccountButton.performClick();
// assert controller has been called properly
verify(setupController, times(1)).setPassword(safePass);
verify(setupController, times(1)).showDozeOrCreateAccount();
}
@Test
public void testStrengthMeterUI() {
// Test the meters' progress and color for several values
testStrengthMeter("1234567890ab", STRONG, StrengthMeter.GREEN);
testStrengthMeter("123456789", QUITE_STRONG, StrengthMeter.LIME);
testStrengthMeter("123456", QUITE_WEAK, StrengthMeter.YELLOW);
testStrengthMeter("123", WEAK, StrengthMeter.ORANGE);
testStrengthMeter("", NONE, StrengthMeter.RED);
}
private void testStrengthMeter(String pass, float strength, int color) {
passwordEntry.setText(pass);
assertEquals(strengthMeter.getProgress(),
(int) (strengthMeter.getMax() * strength));
assertEquals(color, strengthMeter.getColor());
}
@Test
public void testPasswordMatchUI() {
// Password mismatch
passwordEntry.setText("really.safe.password");
passwordConfirmation.setText("really.safe.pass");
assertEquals(createAccountButton.isEnabled(), false);
assertEquals(passwordConfirmationWrapper.getError(),
passwordFragment.getString(R.string.passwords_do_not_match));
// Button enabled
passwordEntry.setText("really.safe.pass");
passwordConfirmation.setText("really.safe.pass");
// Confirm that the password mismatch error message is not visible
assertNotEquals(passwordConfirmationWrapper.getError(),
passwordFragment.getString(R.string.passwords_do_not_match));
// Passwords match, so button should be enabled
assertEquals(createAccountButton.isEnabled(), true);
}
}

View File

@@ -1,10 +1,6 @@
package org.briarproject.briar.android.login;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.support.design.widget.TextInputLayout;
import android.widget.Button;
import android.widget.EditText;
import com.google.common.base.Strings;
@@ -13,37 +9,16 @@ import org.briarproject.bramble.api.identity.AuthorConstants;
import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.R;
import org.briarproject.briar.android.TestBriarApplication;
import org.briarproject.briar.android.controller.handler.ResultHandler;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowActivity;
import static junit.framework.Assert.assertEquals;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.NONE;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_STRONG;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.STRONG;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.WEAK;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21,
@@ -51,95 +26,18 @@ import static org.robolectric.Shadows.shadowOf;
packageName = "org.briarproject.briar")
public class SetupActivityTest {
private static final int TIMEOUT_MS = 10 * 1000;
private TestSetupActivity setupActivity;
private SetupActivity setupActivity;
private TextInputLayout nicknameEntryWrapper;
private TextInputLayout passwordConfirmationWrapper;
private EditText nicknameEntry;
private EditText passwordEntry;
private EditText passwordConfirmation;
private StrengthMeter strengthMeter;
private Button createAccountButton;
@Mock
private SetupController setupController;
@Captor
private ArgumentCaptor<ResultHandler<Void>> authorCaptor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
setupActivity = Robolectric.setupActivity(TestSetupActivity.class);
setupActivity = Robolectric.setupActivity(SetupActivity.class);
nicknameEntryWrapper = (TextInputLayout) setupActivity
.findViewById(R.id.nickname_entry_wrapper);
passwordConfirmationWrapper = (TextInputLayout) setupActivity
.findViewById(R.id.password_confirm_wrapper);
nicknameEntry =
(EditText) setupActivity.findViewById(R.id.nickname_entry);
passwordEntry =
(EditText) setupActivity.findViewById(R.id.password_entry);
passwordConfirmation =
(EditText) setupActivity.findViewById(R.id.password_confirm);
strengthMeter =
(StrengthMeter) setupActivity.findViewById(R.id.strength_meter);
createAccountButton =
(Button) setupActivity.findViewById(R.id.create_account);
}
private void testStrengthMeter(String pass, float strength, int color) {
passwordEntry.setText(pass);
assertEquals(strengthMeter.getProgress(),
(int) (strengthMeter.getMax() * strength));
assertEquals(color, strengthMeter.getColor());
}
@Test
public void testPasswordMatchUI() {
// Password mismatch
passwordEntry.setText("really.safe.password");
passwordConfirmation.setText("really.safe.pass");
assertEquals(createAccountButton.isEnabled(), false);
assertEquals(passwordConfirmationWrapper.getError(),
setupActivity.getString(R.string.passwords_do_not_match));
// Button enabled
passwordEntry.setText("really.safe.pass");
passwordConfirmation.setText("really.safe.pass");
// Confirm that the password mismatch error message is not visible
Assert.assertNotEquals(passwordConfirmationWrapper.getError(),
setupActivity.getString(R.string.passwords_do_not_match));
// Nick has not been set, expect the button to be disabled
assertEquals(createAccountButton.isEnabled(), false);
}
@Test
public void testCreateAccountUI() {
SetupController mockedController = this.setupController;
setupActivity.setController(mockedController);
// Mock strong password strength answer
when(mockedController.estimatePasswordStrength(anyString())).thenReturn(
STRONG);
String safePass = "really.safe.password";
String nick = "nick.nickerton";
passwordEntry.setText(safePass);
passwordConfirmation.setText(safePass);
nicknameEntry.setText(nick);
// Confirm that the create account button is clickable
assertEquals(createAccountButton.isEnabled(), true);
createAccountButton.performClick();
// Verify that the controller's method was called with the correct
// params and get the callback
verify(mockedController, times(1))
.storeAuthorInfo(eq(nick), eq(safePass),
authorCaptor.capture());
authorCaptor.getValue().onResult(null);
// execute the callback
assertEquals(setupActivity.isFinishing(), true);
// Confirm that the correct Activity has been started
ShadowActivity shadowActivity = shadowOf(setupActivity);
Intent intent = shadowActivity.peekNextStartedActivity();
assertEquals(intent.getComponent().getClassName(),
NavDrawerActivity.class.getName());
}
@Test
@@ -153,74 +51,4 @@ public class SetupActivityTest {
assertEquals(nicknameEntryWrapper.getError(),
setupActivity.getString(R.string.name_too_long));
}
@Test
public void testAccountCreation() {
SetupController controller = setupActivity.getController();
// mock a resulthandler
ResultHandler<Void> resultHandler =
(ResultHandler<Void>) mock(ResultHandler.class);
controller.storeAuthorInfo("nick", "some.strong.pass", resultHandler);
// blocking verification call with timeout that waits until the mocked
// result gets called with handle 0L, the expected value
verify(resultHandler, timeout(TIMEOUT_MS).times(1)).onResult(null);
SharedPreferences prefs =
setupActivity.getSharedPreferences("db", Context.MODE_PRIVATE);
// Confirm database key
assertTrue(prefs.contains("key"));
// Note that Robolectric uses its own persistant storage that it
// wipes clean after each test run, no need to clean up manually.
}
@Test
public void testStrengthMeter() {
SetupController controller = setupActivity.getController();
String strongPass = "very.strong.password.123";
String weakPass = "we";
String quiteStrongPass = "quite.strong";
float val = controller.estimatePasswordStrength(strongPass);
assertTrue(val == STRONG);
val = controller.estimatePasswordStrength(weakPass);
assertTrue(val < WEAK && val > NONE);
val = controller.estimatePasswordStrength(quiteStrongPass);
assertTrue(val < STRONG && val > QUITE_WEAK);
}
@Test
public void testStrengthMeterUI() {
Assert.assertNotNull(setupActivity);
// replace the setup controller with our mocked copy
SetupController mockedController = this.setupController;
setupActivity.setController(mockedController);
// Mock answers for UI testing only
when(mockedController.estimatePasswordStrength("strong")).thenReturn(
STRONG);
when(mockedController.estimatePasswordStrength("qstrong")).thenReturn(
QUITE_STRONG);
when(mockedController.estimatePasswordStrength("qweak")).thenReturn(
QUITE_WEAK);
when(mockedController.estimatePasswordStrength("weak")).thenReturn(
WEAK);
when(mockedController.estimatePasswordStrength("empty")).thenReturn(
NONE);
// Test the meters progress and color for several values
testStrengthMeter("strong", STRONG, StrengthMeter.GREEN);
Mockito.verify(mockedController, Mockito.times(1))
.estimatePasswordStrength(eq("strong"));
testStrengthMeter("qstrong", QUITE_STRONG, StrengthMeter.LIME);
Mockito.verify(mockedController, Mockito.times(1))
.estimatePasswordStrength(eq("qstrong"));
testStrengthMeter("qweak", QUITE_WEAK, StrengthMeter.YELLOW);
Mockito.verify(mockedController, Mockito.times(1))
.estimatePasswordStrength(eq("qweak"));
testStrengthMeter("weak", WEAK, StrengthMeter.ORANGE);
Mockito.verify(mockedController, Mockito.times(1))
.estimatePasswordStrength(eq("weak"));
// Not sure this should be the correct behaviour on an empty input ?
testStrengthMeter("empty", NONE, StrengthMeter.RED);
Mockito.verify(mockedController, Mockito.times(1))
.estimatePasswordStrength(eq("empty"));
}
}

View File

@@ -0,0 +1,86 @@
package org.briarproject.briar.android.login;
import android.content.SharedPreferences;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.briar.android.controller.handler.ResultHandler;
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.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
public class SetupControllerImplTest extends BrambleMockTestCase {
private final SharedPreferences briarPrefs =
context.mock(SharedPreferences.class);
private final DatabaseConfig databaseConfig =
context.mock(DatabaseConfig.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final PasswordStrengthEstimator estimator =
context.mock(PasswordStrengthEstimator.class);
private final SharedPreferences.Editor editor =
context.mock(SharedPreferences.Editor.class);
private final SetupActivity setupActivity;
private final Executor cryptoExecutor = new ImmediateExecutor();
private final String authorName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
private final String password = "some.strong.pass";
private final String encryptedHex = "010203";
private final byte[] encryptedBytes = new byte[] {1, 2, 3};
private final SecretKey key = getSecretKey();
public SetupControllerImplTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
setupActivity = context.mock(SetupActivity.class);
}
@Test
public void testCreateAccount() {
context.checking(new Expectations() {{
// Setting the author name shows the password fragment
oneOf(setupActivity).showPasswordFragment();
// Generate a database key
oneOf(crypto).generateSecretKey();
will(returnValue(key));
// Attach the author name and database key to the database config
oneOf(databaseConfig).setLocalAuthorName(authorName);
oneOf(databaseConfig).setEncryptionKey(key);
// Encrypt the key with the password
oneOf(crypto).encryptWithPassword(key.getBytes(), password);
will(returnValue(encryptedBytes));
// Store the encrypted key
oneOf(briarPrefs).edit();
will(returnValue(editor));
oneOf(editor).putString("key", encryptedHex);
oneOf(editor).apply();
}});
SetupControllerImpl s = new SetupControllerImpl(briarPrefs,
databaseConfig, cryptoExecutor, crypto, estimator);
s.setSetupActivity(setupActivity);
final AtomicBoolean called = new AtomicBoolean(false);
s.setAuthorName(authorName);
s.setPassword(password);
s.createAccount(new ResultHandler<Void>() {
@Override
public void onResult(Void result) {
called.set(true);
}
});
assertTrue(called.get());
}
}

View File

@@ -1,24 +1,13 @@
package org.briarproject.briar.android.login;
/**
* This class exposes the PasswordController and SetupController and offers the
* possibility to override them.
* This class exposes the PasswordController and offers the possibility to
* replace it.
*/
public class TestChangePasswordActivity extends ChangePasswordActivity {
public PasswordController getPasswordController() {
return passwordController;
}
public SetupController getSetupController() {
return setupController;
}
public void setPasswordController(PasswordController passwordController) {
this.passwordController = passwordController;
}
public void setSetupController(SetupController setupController) {
this.setupController = setupController;
}
}

View File

@@ -1,16 +0,0 @@
package org.briarproject.briar.android.login;
/**
* This class exposes the SetupController and offers the possibility to
* override it.
*/
public class TestSetupActivity extends SetupActivity {
SetupController getController() {
return setupController;
}
void setController(SetupController setupController) {
this.setupController = setupController;
}
}

View File

@@ -4,6 +4,9 @@ allprojects {
repositories {
jcenter()
mavenLocal()
maven {
url 'https://maven.google.com'
}
}
}