Merge pull request #144 from andOTP/intro-screen

Intro screen
This commit is contained in:
Jakob Nixdorf 2018-02-28 16:30:47 +01:00 committed by GitHub
commit cb442d95d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 677 additions and 115 deletions

View file

@ -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)

View file

@ -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"

View file

@ -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"

View file

@ -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;
}
}
}
}

View file

@ -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) {

View file

@ -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;

View file

@ -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);
}

View 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>

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View 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>

View file

@ -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>

View file

@ -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" }
}
}