commit
cb442d95d8
15 changed files with 677 additions and 115 deletions
|
@ -128,6 +128,7 @@ So make sure you have a **current backup** before switching!
|
|||
* [Apache Commons Codec](https://commons.apache.org/proper/commons-codec/)
|
||||
* [Expandable Layout](https://github.com/AAkira/ExpandableLayout)
|
||||
* [LicensesDialog](https://github.com/PSDev/LicensesDialog)
|
||||
* [material-intro](https://github.com/heinrichreimer/material-intro)
|
||||
* [MaterialProgressBar](https://github.com/DreaminginCodeZH/MaterialProgressBar)
|
||||
* [OpenPGP API library](https://github.com/open-keychain/openpgp-api)
|
||||
* [VNTNumberPickerPreference](https://github.com/vanniktech/VNTNumberPickerPreference)
|
||||
|
|
|
@ -25,6 +25,9 @@ android {
|
|||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
dataBinding {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
|
@ -41,6 +44,7 @@ dependencies {
|
|||
compile "com.android.support:recyclerview-v7:$supportLibVersion"
|
||||
compile "com.android.support.constraint:constraint-layout:1.0.2"
|
||||
compile "com.github.aakira:expandable-layout:1.6.0"
|
||||
compile "com.heinrichreimersoftware:material-intro:1.6.2"
|
||||
compile "com.journeyapps:zxing-android-embedded:3.5.0"
|
||||
compile "com.vanniktech:vntnumberpickerpreference:1.0.0"
|
||||
compile "de.psdev.licensesdialog:licensesdialog:1.8.3"
|
||||
|
|
|
@ -35,6 +35,10 @@
|
|||
android:name=".Activities.BackupActivity"
|
||||
android:parentActivityName=".Activities.MainActivity"
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
<activity
|
||||
android:name=".Activities.IntroScreenActivity"
|
||||
android:parentActivityName=".Activities.MainActivity"
|
||||
android:theme="@style/Theme.Intro" />
|
||||
<activity
|
||||
android:name=".Activities.SettingsActivity"
|
||||
android:parentActivityName=".Activities.MainActivity"
|
||||
|
|
|
@ -0,0 +1,481 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Jakob Nixdorf
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.shadowice.flocke.andotp.Activities;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.KeyguardManager;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.TextInputEditText;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.heinrichreimersoftware.materialintro.app.IntroActivity;
|
||||
import com.heinrichreimersoftware.materialintro.app.OnNavigationBlockedListener;
|
||||
import com.heinrichreimersoftware.materialintro.app.SlideFragment;
|
||||
import com.heinrichreimersoftware.materialintro.slide.FragmentSlide;
|
||||
import com.heinrichreimersoftware.materialintro.slide.SimpleSlide;
|
||||
|
||||
import org.shadowice.flocke.andotp.R;
|
||||
import org.shadowice.flocke.andotp.Utilities.Constants;
|
||||
import org.shadowice.flocke.andotp.Utilities.Settings;
|
||||
import org.shadowice.flocke.andotp.Utilities.UIHelper;
|
||||
|
||||
public class IntroScreenActivity extends IntroActivity {
|
||||
private Settings settings;
|
||||
|
||||
private EncryptionFragment encryptionFragment;
|
||||
private AuthenticationFragment authenticationFragment;
|
||||
|
||||
private void saveSettings() {
|
||||
Constants.EncryptionType encryptionType = encryptionFragment.getEncryptionType();
|
||||
Constants.AuthMethod authMethod = authenticationFragment.getAuthMethod();
|
||||
|
||||
settings.setEncryption(encryptionType);
|
||||
settings.setAuthMethod(authMethod);
|
||||
|
||||
if (authMethod == Constants.AuthMethod.PASSWORD || authMethod == Constants.AuthMethod.PIN) {
|
||||
String password = authenticationFragment.getPassword();
|
||||
settings.setAuthCredentials(password);
|
||||
}
|
||||
|
||||
settings.setFirstTimeWarningShown(true);
|
||||
}
|
||||
|
||||
@Override protected void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
settings = new Settings(this);
|
||||
|
||||
encryptionFragment = new EncryptionFragment();
|
||||
authenticationFragment = new AuthenticationFragment();
|
||||
|
||||
encryptionFragment.setEncryptionChangedCallback(new EncryptionFragment.EncryptionChangedCallback() {
|
||||
@Override
|
||||
public void onEncryptionChanged(Constants.EncryptionType newEncryptionType) {
|
||||
authenticationFragment.updateEncryptionType(newEncryptionType);
|
||||
}
|
||||
});
|
||||
|
||||
setButtonBackFunction(BUTTON_BACK_FUNCTION_BACK);
|
||||
|
||||
addSlide(new SimpleSlide.Builder()
|
||||
.title(R.string.intro_slide1_title)
|
||||
.description(R.string.intro_slide1_desc)
|
||||
.background(R.color.colorPrimary)
|
||||
.backgroundDark(R.color.colorPrimaryDark)
|
||||
.canGoBackward(false)
|
||||
.scrollable(false)
|
||||
.build()
|
||||
);
|
||||
|
||||
addSlide(new FragmentSlide.Builder()
|
||||
.background(R.color.colorPrimary)
|
||||
.backgroundDark(R.color.colorPrimaryDark)
|
||||
.fragment(encryptionFragment)
|
||||
.build()
|
||||
);
|
||||
|
||||
// Tell the fragment where it is located
|
||||
authenticationFragment.setSlidePos(getSlides().size());
|
||||
|
||||
addSlide(new FragmentSlide.Builder()
|
||||
.background(R.color.colorPrimary)
|
||||
.backgroundDark(R.color.colorPrimaryDark)
|
||||
.fragment(authenticationFragment)
|
||||
.build()
|
||||
);
|
||||
|
||||
addSlide(new SimpleSlide.Builder()
|
||||
.title(R.string.intro_slide4_title)
|
||||
.description(R.string.intro_slide4_desc)
|
||||
.background(R.color.colorPrimary)
|
||||
.backgroundDark(R.color.colorPrimaryDark)
|
||||
.scrollable(false)
|
||||
.build()
|
||||
);
|
||||
|
||||
addOnNavigationBlockedListener(new OnNavigationBlockedListener() {
|
||||
@Override
|
||||
public void onNavigationBlocked(int position, int direction) {
|
||||
if (position == 2)
|
||||
authenticationFragment.flashWarning();
|
||||
}
|
||||
});
|
||||
|
||||
addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
if (position == 3)
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// We don't want users to quit the intro screen and end up in an uninitialized state
|
||||
}
|
||||
|
||||
public static class EncryptionFragment extends SlideFragment {
|
||||
private EncryptionChangedCallback encryptionChangedCallback = null;
|
||||
|
||||
private Spinner selection;
|
||||
private TextView desc;
|
||||
|
||||
private SparseArray<Constants.EncryptionType> selectionMapping;
|
||||
|
||||
public EncryptionFragment() {
|
||||
}
|
||||
|
||||
public void setEncryptionChangedCallback(EncryptionChangedCallback cb) {
|
||||
encryptionChangedCallback = cb;
|
||||
}
|
||||
|
||||
private void generateSelectionMapping() {
|
||||
String[] encValues = getResources().getStringArray(R.array.settings_values_encryption);
|
||||
|
||||
selectionMapping = new SparseArray<>();
|
||||
for (int i = 0; i < encValues.length; i++)
|
||||
selectionMapping.put(i, Constants.EncryptionType.valueOf(encValues[i].toUpperCase()));
|
||||
}
|
||||
|
||||
public Constants.EncryptionType getEncryptionType() {
|
||||
return selectionMapping.get(selection.getSelectedItemPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.component_intro_encryption, container, false);
|
||||
|
||||
selection = root.findViewById(R.id.introEncryptionSelection);
|
||||
desc = root.findViewById(R.id.introEncryptionDesc);
|
||||
|
||||
generateSelectionMapping();
|
||||
|
||||
selection.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
Constants.EncryptionType encryptionType = selectionMapping.get(i);
|
||||
|
||||
if (encryptionType == Constants.EncryptionType.PASSWORD)
|
||||
desc.setText(R.string.intro_slide2_desc_password);
|
||||
else if (encryptionType == Constants.EncryptionType.KEYSTORE)
|
||||
desc.setText(R.string.intro_slide2_desc_keystore);
|
||||
|
||||
if (encryptionChangedCallback != null)
|
||||
encryptionChangedCallback.onEncryptionChanged(encryptionType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
public interface EncryptionChangedCallback {
|
||||
void onEncryptionChanged(Constants.EncryptionType newEncryptionType);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AuthenticationFragment extends SlideFragment {
|
||||
private Constants.EncryptionType encryptionType = Constants.EncryptionType.KEYSTORE;
|
||||
|
||||
private int slidePos = -1;
|
||||
|
||||
private int minLength = Constants.AUTH_MIN_PASSWORD_LENGTH;
|
||||
private String lengthWarning = "";
|
||||
private String noPasswordWarning = "";
|
||||
private String confirmPasswordWarning = "";
|
||||
|
||||
private TextView desc = null;
|
||||
private Spinner selection = null;
|
||||
private TextView authWarnings = null;
|
||||
private LinearLayout credentialsLayout = null;
|
||||
private TextInputLayout passwordLayout = null;
|
||||
private TextInputEditText passwordInput = null;
|
||||
private EditText passwordConfirm = null;
|
||||
|
||||
private SparseArray<Constants.AuthMethod> selectionMapping;
|
||||
|
||||
public AuthenticationFragment() {
|
||||
}
|
||||
|
||||
public void setSlidePos(int pos) {
|
||||
slidePos = pos;
|
||||
}
|
||||
|
||||
public void updateEncryptionType(Constants.EncryptionType encryptionType) {
|
||||
this.encryptionType = encryptionType;
|
||||
|
||||
if (desc != null) {
|
||||
if (encryptionType == Constants.EncryptionType.KEYSTORE) {
|
||||
desc.setText(R.string.intro_slide3_desc_keystore);
|
||||
} else if (encryptionType == Constants.EncryptionType.PASSWORD) {
|
||||
desc.setText(R.string.intro_slide3_desc_password);
|
||||
|
||||
Constants.AuthMethod selectedMethod = selectionMapping.get(selection.getSelectedItemPosition());
|
||||
if (selectedMethod != Constants.AuthMethod.PASSWORD && selectedMethod != Constants.AuthMethod.PIN )
|
||||
selection.setSelection(selectionMapping.indexOfValue(Constants.AuthMethod.PASSWORD));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateSelectionMapping() {
|
||||
Constants.AuthMethod[] authValues = Constants.AuthMethod.values();
|
||||
|
||||
selectionMapping = new SparseArray<>();
|
||||
for (int i = 0; i < authValues.length; i++)
|
||||
selectionMapping.put(i, authValues[i]);
|
||||
}
|
||||
|
||||
private void displayWarning(int resId) {
|
||||
displayWarning(getString(resId));
|
||||
}
|
||||
|
||||
private void displayWarning(String warning) {
|
||||
authWarnings.setVisibility(View.VISIBLE);
|
||||
authWarnings.setText(warning);
|
||||
}
|
||||
|
||||
private void hideWarning() {
|
||||
authWarnings.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void flashWarning() {
|
||||
if (authWarnings.getVisibility() == View.VISIBLE) {
|
||||
ObjectAnimator animator = ObjectAnimator.ofInt(authWarnings, "backgroundColor",
|
||||
Color.TRANSPARENT, getResources().getColor(R.color.colorAccent), Color.TRANSPARENT);
|
||||
animator.setDuration(500);
|
||||
animator.setRepeatCount(0);
|
||||
animator.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
animator.setEvaluator(new ArgbEvaluator());
|
||||
animator.start();
|
||||
}
|
||||
}
|
||||
|
||||
public Constants.AuthMethod getAuthMethod() {
|
||||
return selectionMapping.get(selection.getSelectedItemPosition());
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return passwordInput.getText().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
final View root = inflater.inflate(R.layout.component_intro_authentication, container, false);
|
||||
|
||||
desc = root.findViewById(R.id.introAuthDesc);
|
||||
selection = root.findViewById(R.id.introAuthSelection);
|
||||
authWarnings = root.findViewById(R.id.introAuthWarnings);
|
||||
credentialsLayout = root.findViewById(R.id.introCredentialsLayout);
|
||||
passwordLayout = root.findViewById(R.id.introPasswordLayout);
|
||||
passwordInput = root.findViewById(R.id.introPasswordEdit);
|
||||
passwordConfirm = root.findViewById(R.id.introPasswordConfirm);
|
||||
|
||||
generateSelectionMapping();
|
||||
|
||||
final String[] authEntries = getResources().getStringArray(R.array.settings_entries_auth);
|
||||
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(getIntroActivity(), android.R.layout.simple_spinner_item, authEntries) {
|
||||
@Override
|
||||
public boolean isEnabled(int position){
|
||||
return encryptionType != Constants.EncryptionType.PASSWORD ||
|
||||
position == selectionMapping.indexOfValue(Constants.AuthMethod.PASSWORD) ||
|
||||
position == selectionMapping.indexOfValue(Constants.AuthMethod.PIN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, View convertView, @NonNull ViewGroup parent) {
|
||||
View view = super.getDropDownView(position, convertView, parent);
|
||||
TextView tv = (TextView) view;
|
||||
|
||||
tv.setEnabled(encryptionType != Constants.EncryptionType.PASSWORD ||
|
||||
position == selectionMapping.indexOfValue(Constants.AuthMethod.PASSWORD) ||
|
||||
position == selectionMapping.indexOfValue(Constants.AuthMethod.PIN));
|
||||
|
||||
return view;
|
||||
}
|
||||
};
|
||||
|
||||
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
selection.setAdapter(spinnerArrayAdapter);
|
||||
|
||||
selection.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
Constants.AuthMethod authMethod = selectionMapping.get(i);
|
||||
|
||||
if (authMethod == Constants.AuthMethod.PASSWORD) {
|
||||
credentialsLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
passwordLayout.setHint(getString(R.string.settings_hint_password));
|
||||
passwordConfirm.setHint(R.string.settings_hint_password_confirm);
|
||||
|
||||
passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
passwordConfirm.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
|
||||
passwordInput.setTransformationMethod(new PasswordTransformationMethod());
|
||||
passwordConfirm.setTransformationMethod(new PasswordTransformationMethod());
|
||||
|
||||
minLength = Constants.AUTH_MIN_PASSWORD_LENGTH;
|
||||
lengthWarning = getString(R.string.settings_label_short_password, minLength);
|
||||
noPasswordWarning = getString(R.string.intro_slide3_warn_no_password);
|
||||
confirmPasswordWarning = getString(R.string.intro_slide3_warn_confirm_password);
|
||||
|
||||
if (getIntroActivity().getCurrentSlidePosition() == slidePos) {
|
||||
passwordInput.requestFocus();
|
||||
UIHelper.showKeyboard(getContext(), passwordInput);
|
||||
}
|
||||
} else if (authMethod == Constants.AuthMethod.PIN) {
|
||||
credentialsLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
passwordLayout.setHint(getString(R.string.settings_hint_pin));
|
||||
passwordConfirm.setHint(R.string.settings_hint_pin_confirm);
|
||||
|
||||
passwordInput.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
|
||||
passwordConfirm.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
|
||||
|
||||
passwordInput.setTransformationMethod(new PasswordTransformationMethod());
|
||||
passwordConfirm.setTransformationMethod(new PasswordTransformationMethod());
|
||||
|
||||
minLength = Constants.AUTH_MIN_PIN_LENGTH;
|
||||
lengthWarning = getString(R.string.settings_label_short_pin, minLength);
|
||||
noPasswordWarning = getString(R.string.intro_slide3_warn_no_pin);
|
||||
confirmPasswordWarning = getString(R.string.intro_slide3_warn_confirm_pin);
|
||||
|
||||
if (getIntroActivity().getCurrentSlidePosition() == slidePos) {
|
||||
passwordInput.requestFocus();
|
||||
UIHelper.showKeyboard(getContext(), passwordInput);
|
||||
}
|
||||
} else {
|
||||
credentialsLayout.setVisibility(View.INVISIBLE);
|
||||
|
||||
UIHelper.hideKeyboard(getIntroActivity(), root);
|
||||
}
|
||||
|
||||
updateNavigation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
TextWatcher textWatcher = new TextWatcher() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
updateNavigation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
}
|
||||
};
|
||||
|
||||
passwordInput.addTextChangedListener(textWatcher);
|
||||
passwordConfirm.addTextChangedListener(textWatcher);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGoForward() {
|
||||
Constants.AuthMethod authMethod = selectionMapping.get(selection.getSelectedItemPosition());
|
||||
|
||||
if (authMethod == Constants.AuthMethod.PIN || authMethod == Constants.AuthMethod.PASSWORD) {
|
||||
String password = passwordInput.getText().toString();
|
||||
String confirm = passwordConfirm.getText().toString();
|
||||
|
||||
if (! password.isEmpty()) {
|
||||
if (password.length() < minLength) {
|
||||
displayWarning(lengthWarning);
|
||||
return false;
|
||||
} else {
|
||||
if (! confirm.isEmpty() && confirm.equals(password)) {
|
||||
hideWarning();
|
||||
return true;
|
||||
} else {
|
||||
displayWarning(confirmPasswordWarning);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
displayWarning(noPasswordWarning);
|
||||
return false;
|
||||
}
|
||||
} else if (authMethod == Constants.AuthMethod.DEVICE) {
|
||||
KeyguardManager km = (KeyguardManager) getContext().getSystemService(KEYGUARD_SERVICE);
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||
displayWarning(R.string.settings_toast_auth_device_pre_lollipop);
|
||||
return false;
|
||||
} else if (! km.isKeyguardSecure()) {
|
||||
displayWarning(R.string.settings_toast_auth_device_not_secure);
|
||||
return false;
|
||||
}
|
||||
|
||||
hideWarning();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
hideWarning();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,7 +44,6 @@ import android.support.v7.widget.helper.ItemTouchHelper;
|
|||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.widget.AdapterView;
|
||||
|
@ -104,37 +103,8 @@ public class MainActivity extends BaseActivity
|
|||
}
|
||||
|
||||
private void showFirstTimeWarning() {
|
||||
ViewGroup container = findViewById(R.id.main_content);
|
||||
View msgView = getLayoutInflater().inflate(R.layout.dialog_database_encryption, container, false);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.dialog_title_encryption)
|
||||
.setView(msgView)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
settings.setFirstTimeWarningShown(true);
|
||||
updateEncryption(null);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.button_settings, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
settings.setFirstTimeWarningShown(true);
|
||||
|
||||
Intent settingsIntent = new Intent(getBaseContext(), SettingsActivity.class);
|
||||
startActivityForResult(settingsIntent, Constants.INTENT_MAIN_SETTINGS);
|
||||
}
|
||||
})
|
||||
.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialogInterface) {
|
||||
settings.setFirstTimeWarningShown(true);
|
||||
updateEncryption(null);
|
||||
}
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
Intent introIntent = new Intent(this, IntroScreenActivity.class);
|
||||
startActivityForResult(introIntent, Constants.INTENT_MAIN_INTRO);
|
||||
}
|
||||
|
||||
public void authenticate(int messageId) {
|
||||
|
|
|
@ -52,6 +52,7 @@ public class Constants {
|
|||
public final static int INTENT_MAIN_AUTHENTICATE = 100;
|
||||
public final static int INTENT_MAIN_SETTINGS = 101;
|
||||
public final static int INTENT_MAIN_BACKUP = 102;
|
||||
public final static int INTENT_MAIN_INTRO = 103;
|
||||
|
||||
public final static int INTENT_BACKUP_OPEN_DOCUMENT_PLAIN = 200;
|
||||
public final static int INTENT_BACKUP_SAVE_DOCUMENT_PLAIN = 201;
|
||||
|
|
|
@ -220,6 +220,10 @@ public class Settings {
|
|||
return AuthMethod.valueOf(authString.toUpperCase());
|
||||
}
|
||||
|
||||
public void setAuthMethod(AuthMethod authMethod) {
|
||||
setString(R.string.settings_key_auth, authMethod.name().toLowerCase());
|
||||
}
|
||||
|
||||
public void removeAuthPasswordHash() {
|
||||
remove(R.string.settings_key_auth_password_hash);
|
||||
}
|
||||
|
@ -290,6 +294,10 @@ public class Settings {
|
|||
return EncryptionType.valueOf(encType.toUpperCase());
|
||||
}
|
||||
|
||||
public void setEncryption(EncryptionType encryptionType) {
|
||||
setEncryption(encryptionType.name().toLowerCase());
|
||||
}
|
||||
|
||||
public void setEncryption(String encryption) {
|
||||
setString(R.string.settings_key_encryption, encryption);
|
||||
}
|
||||
|
|
87
app/src/main/res/layout/component_intro_authentication.xml
Normal file
87
app/src/main/res/layout/component_intro_authentication.xml
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v4.widget.NestedScrollView
|
||||
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:overScrollMode="never">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:padding="@dimen/activity_margin_large">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/settings_title_auth" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/introAuthDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_margin"
|
||||
android:text="@string/intro_slide3_desc_keystore"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/introAuthSelection"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_margin_large"
|
||||
android:layout_marginStart="@dimen/activity_margin"
|
||||
android:layout_marginEnd="@dimen/activity_margin"
|
||||
android:entries="@array/settings_entries_auth" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/introAuthWarnings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_margin"
|
||||
android:padding="@dimen/activity_margin_small"
|
||||
android:visibility="gone"
|
||||
android:textColor="@color/warning_red"
|
||||
android:textAlignment="center"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/introCredentialsLayout"
|
||||
android:visibility="invisible"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/activity_margin_large"
|
||||
android:paddingStart="@dimen/activity_margin"
|
||||
android:paddingEnd="@dimen/activity_margin">
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/introPasswordLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/settings_hint_password"
|
||||
app:passwordToggleEnabled="true" >
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/introPasswordEdit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/introPasswordConfirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/settings_hint_password_confirm"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v4.widget.NestedScrollView>
|
40
app/src/main/res/layout/component_intro_encryption.xml
Normal file
40
app/src/main/res/layout/component_intro_encryption.xml
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:padding="@dimen/activity_margin_large" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/settings_title_encryption" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_margin"
|
||||
android:text="@string/intro_slide2_desc"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/introEncryptionSelection"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_margin_large"
|
||||
android:layout_marginStart="@dimen/activity_margin"
|
||||
android:layout_marginEnd="@dimen/activity_margin"
|
||||
android:entries="@array/settings_entries_encryption" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/introEncryptionDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/activity_margin_large"
|
||||
android:text="@string/intro_slide2_desc_keystore"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -1,60 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="@dimen/activity_margin"
|
||||
android:paddingBottom="@dimen/activity_margin_small">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="@dimen/activity_margin_medium"
|
||||
android:layout_marginEnd="@dimen/activity_margin_medium">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="@string/dialog_msg_security_first" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_margin_small"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/dialog_title_security_keystore"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/activity_margin_small"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="@string/dialog_msg_security_keystore" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_margin_small"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/dialog_title_security_password"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/activity_margin_small"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="@string/dialog_msg_security_password" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_margin_small"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="@string/dialog_msg_security_default" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
|
@ -12,6 +12,12 @@
|
|||
<copyright>Copyright (C) 2015 A.Akira</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>material-intro</name>
|
||||
<url>https://github.com/heinrichreimer/material-intro</url>
|
||||
<copyright>Copyright (c) 2017 Jan Heinrich Reimer</copyright>
|
||||
<license>MIT License</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>MaterialProgressBar</name>
|
||||
<url>https://github.com/DreaminginCodeZH/MaterialProgressBar</url>
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
<color name="paypal_dark">#003087</color>
|
||||
<color name="paypal_light">#009cde</color>
|
||||
|
||||
<color name="warning_red">#b71c1c</color> <!-- material_red_900 -->
|
||||
|
||||
<color name="dark_thumbnail_background">#ffe0e0e0</color> <!-- material_grey_300 -->
|
||||
|
||||
<color name="fab_small_label_background">#212121</color>
|
||||
|
|
39
app/src/main/res/values/strings_intro.xml
Normal file
39
app/src/main/res/values/strings_intro.xml
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="intro_slide1_title">Let\'s get started</string>
|
||||
<string name="intro_slide1_desc">Welcome to <b>andOTP</b>, this wizard will guide you through
|
||||
the initial setup. Please pay attention and read carefully otherwise you could lose data!
|
||||
\n\nDon\'t worry, you can still change any of the options later in the <b>Settings</b>.</string>
|
||||
|
||||
<string name="intro_slide2_desc">To ensure the security of your accounts <b>andOTP</b> only
|
||||
stores them in encrypted data files. Here you can choose which method of encryption will be
|
||||
used.</string>
|
||||
<string name="intro_slide2_desc_keystore">The KeyStore is a system component of Android for
|
||||
securely storing cryptographic keys. The advantage of this method is that the keys are
|
||||
stored separated from the data files and can be backed by hardware cryptography (if your
|
||||
device supports it). However, as the keys are not stored with the apps data, <b>this method
|
||||
prevents external backup solutions (like Titanium) from working</b>. If you choose this
|
||||
method you will have to rely on the internal backup functions provided by <b>andOTP</b>.
|
||||
\n\n<b>Warning</b>: The KeyStore is known to cause problems on some custom ROMs (and a few
|
||||
stock ones as well). If you don\'t mind entering a password / PIN every time you start
|
||||
<b>andOTP</b> it is highly recommended to use the password-based encryption instead.</string>
|
||||
<string name="intro_slide2_desc_password">This method will encrypt your data with a key
|
||||
generated from a password or PIN. The main advantage here is that this will work with
|
||||
external backup solutions (like Titanium) and is much less failure-prone than the KeyStore.
|
||||
However, you will have to enter your credentials every time you start <b>andOTP</b>.</string>
|
||||
|
||||
<string name="intro_slide3_desc_keystore">Here you can setup an authentication to lock
|
||||
<b>andOTP</b>. Since you have chosen <b>Android KeyStore</b> as encryption method this is
|
||||
optional.</string>
|
||||
<string name="intro_slide3_desc_password">Here you can setup an authentication to lock
|
||||
<b>andOTP</b>. Since you have chosen <b>Password / PIN</b> as encryption method you need to
|
||||
set either a password or a PIN to proceed.</string>
|
||||
<string name="intro_slide3_warn_no_password">Please set a password to continue!</string>
|
||||
<string name="intro_slide3_warn_no_pin">Please set a PIN to continue!</string>
|
||||
<string name="intro_slide3_warn_confirm_password">Please confirm your password to continue!</string>
|
||||
<string name="intro_slide3_warn_confirm_pin">Please confirm your PIN to continue!</string>
|
||||
|
||||
<string name="intro_slide4_title">Finished</string>
|
||||
<string name="intro_slide4_desc">Your settings have been saved, you are all set to use
|
||||
<b>andOTP</b> now!</string>
|
||||
</resources>
|
|
@ -65,30 +65,10 @@
|
|||
<string name="dialog_title_rename">Rename</string>
|
||||
<string name="dialog_title_last_used">Last used</string>
|
||||
<string name="dialog_title_keystore_error">KeyStore error</string>
|
||||
<string name="dialog_title_encryption">Database encryption</string>
|
||||
|
||||
<string name="dialog_msg_auth">Please enter your device credentials to start andOTP.</string>
|
||||
<string name="dialog_msg_confirm_delete">Are you sure you want do remove the account \"%1$s\"?</string>
|
||||
|
||||
<string name="dialog_title_security_keystore">1. Android KeyStore</string>
|
||||
<string name="dialog_title_security_password">2. Password / PIN</string>
|
||||
|
||||
<string name="dialog_msg_security_first">To ensure the security of your accounts this app
|
||||
only stores them in encrypted data files using one of the following two methods:</string>
|
||||
<string name="dialog_msg_security_keystore">The KeyStore is a system component of Android for
|
||||
securely storing cryptographic keys. The advantage of this approach is that the keys are
|
||||
stored separated from the data files and can be backed by hardware cryptography (if the
|
||||
hardware supports it). However as the keys are not stored with the apps data this method
|
||||
prevents external backup solutions (like Titanium) from working. If you choose this method
|
||||
you will have to rely on the internal backup functions provided by andOTP.</string>
|
||||
<string name="dialog_msg_security_password">This method will encrypt your data with a key
|
||||
generated from a password or PIN. The main advantage here is that this will work with
|
||||
external backup solutions (like Titanium). However you will have to enter your credentials
|
||||
every time you start andOTP.</string>
|
||||
<string name="dialog_msg_security_default">By default the Android KeyStore will be used, however
|
||||
this is known to cause problems on certain custom ROMs (and a few stock ones as well). You
|
||||
can change the encryption in the <b>Settings</b> by clicking on the button below.</string>
|
||||
|
||||
<string name="dialog_msg_last_used">In order for andOTP to recognize which token was used last
|
||||
you have to have \"tap to reveal\" enabled or use the copy button.\n\nThis message will not
|
||||
be shown again.</string>
|
||||
|
|
|
@ -18,9 +18,8 @@ buildscript {
|
|||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven {
|
||||
url "https://maven.google.com"
|
||||
}
|
||||
maven { url "https://maven.google.com" }
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue