From 8a811717399fa7c6ffd9c53a840a7b80f9c2c420 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 26 Sep 2017 10:04:48 -0300 Subject: [PATCH 1/3] Setup Wizard that asks for Doze Mode exception Keep checking if we are whitelisted and request it if not --- briar-android/build.gradle | 3 + briar-android/src/main/AndroidManifest.xml | 1 + .../android/activity/ActivityComponent.java | 7 + .../briar/android/activity/BaseActivity.java | 18 ++ .../briar/android/activity/BriarActivity.java | 17 -- .../briar/android/activity/RequestCodes.java | 16 +- .../android/login/AuthorNameFragment.java | 84 ++++++++ .../android/login/ChangePasswordActivity.java | 4 +- .../briar/android/login/DozeFragment.java | 95 +++++++++ .../android/login/PasswordController.java | 3 + .../android/login/PasswordControllerImpl.java | 11 +- .../briar/android/login/PasswordFragment.java | 117 ++++++++++ .../briar/android/login/SetupActivity.java | 137 ++---------- .../briar/android/login/SetupController.java | 20 +- .../android/login/SetupControllerImpl.java | 65 +++++- .../briar/android/login/SetupFragment.java | 66 ++++++ .../android/navdrawer/NavDrawerActivity.java | 30 ++- .../briar/android/util/UiUtils.java | 47 ++++- .../res/layout/fragment_setup_author_name.xml | 60 ++++++ .../main/res/layout/fragment_setup_doze.xml | 54 +++++ ..._setup.xml => fragment_setup_password.xml} | 92 ++++---- .../src/main/res/menu/help_action.xml | 12 ++ .../src/main/res/values-bg/strings.xml | 2 +- .../src/main/res/values-ca/strings.xml | 2 +- .../src/main/res/values-de/strings.xml | 2 +- .../src/main/res/values-es/strings.xml | 2 +- .../src/main/res/values-eu/strings.xml | 2 +- .../src/main/res/values-fi/strings.xml | 2 +- .../src/main/res/values-fr/strings.xml | 2 +- .../src/main/res/values-gl/strings.xml | 2 +- .../src/main/res/values-hi/strings.xml | 2 +- .../src/main/res/values-it/strings.xml | 2 +- .../src/main/res/values-nb/strings.xml | 2 +- .../src/main/res/values-oc/strings.xml | 2 +- .../src/main/res/values-pt-rBR/strings.xml | 2 +- .../src/main/res/values-ru/strings.xml | 2 +- .../src/main/res/values-sq/strings.xml | 2 +- .../src/main/res/values-sr/strings.xml | 2 +- .../src/main/res/values-tr/strings.xml | 2 +- .../src/main/res/values-zh-rCN/strings.xml | 2 +- briar-android/src/main/res/values/strings.xml | 12 +- .../login/ChangePasswordActivityTest.java | 63 +++--- .../android/login/SetupActivityTest.java | 199 ++++++++---------- .../login/TestChangePasswordActivity.java | 7 - build.gradle | 3 + 45 files changed, 897 insertions(+), 382 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/login/AuthorNameFragment.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/login/DozeFragment.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/login/SetupFragment.java create mode 100644 briar-android/src/main/res/layout/fragment_setup_author_name.xml create mode 100644 briar-android/src/main/res/layout/fragment_setup_doze.xml rename briar-android/src/main/res/layout/{activity_setup.xml => fragment_setup_password.xml} (54%) create mode 100644 briar-android/src/main/res/menu/help_action.xml diff --git a/briar-android/build.gradle b/briar-android/build.gradle index d31f16b1f..fac88cf8c 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -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' @@ -55,6 +56,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', ] } diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index c43e5aa27..a2fed381f 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -15,6 +15,7 @@ + apps = screenFilterMonitor.getApps(); 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 e148db65b..33ed5bfc3 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 @@ -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); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java index 131b2921b..fe771c430 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java @@ -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; } 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/login/AuthorNameFragment.java new file mode 100644 index 000000000..ab7cd4cd8 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/AuthorNameFragment.java @@ -0,0 +1,84 @@ +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()); + } + +} 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 cea17a2eb..cc460deb7 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 @@ -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), 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/login/DozeFragment.java new file mode 100644 index 000000000..328dac981 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/DozeFragment.java @@ -0,0 +1,95 @@ +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(); + } + +} 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/PasswordController.java index 209b5ac4a..ec3cb7ed7 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/PasswordController.java @@ -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 resultHandler); void changePassword(String password, 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/PasswordControllerImpl.java index ac01eba47..92c80ffe5 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/PasswordControllerImpl.java @@ -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 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 new file mode 100644 index 000000000..f24cf2daf --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java @@ -0,0 +1,117 @@ +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(); + } + +} 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/login/SetupActivity.java index 767a9eb55..17c6f522f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupActivity.java @@ -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(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."); + } + } 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/login/SetupController.java index 5e72acb93..96ea253f8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupController.java @@ -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 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 resultHandler); } 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/login/SetupControllerImpl.java index f413ca736..7fb8a7f7c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupControllerImpl.java @@ -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 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 resultHandler = + new UiResultHandler(setupActivity) { + @Override + public void onResultUi(Void result) { + if (setupActivity == null) + throw new IllegalStateException(); + setupActivity.showApp(); + } + }; + createAccount(resultHandler); + } + + @Override + public void createAccount(final ResultHandler 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); 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/login/SetupFragment.java new file mode 100644 index 000000000..6a30e737a --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupFragment.java @@ -0,0 +1,66 @@ +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 charSequence, int i, int i1, + int i2) { + // noop + } + + @Override + public void onTextChanged(CharSequence authorName, int i, int i1, int i2) { + // noop + } + + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + onClick(textView); + return true; + } + + @Override + public void afterTextChanged(Editable editable) { + // noop + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java index 058d55b55..08c16f784 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java @@ -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,7 +188,7 @@ 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()); @@ -311,6 +321,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); 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 74f182cbc..448252659 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 @@ -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,37 @@ 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; + } + } diff --git a/briar-android/src/main/res/layout/fragment_setup_author_name.xml b/briar-android/src/main/res/layout/fragment_setup_author_name.xml new file mode 100644 index 000000000..8f22bc7ed --- /dev/null +++ b/briar-android/src/main/res/layout/fragment_setup_author_name.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + +