diff --git a/app/build.gradle b/app/build.gradle index 27c1421e..70372839 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,7 +31,6 @@ dependencies { compile 'com.android.support:recyclerview-v7:25.0.0' compile 'com.android.support:appcompat-v7:25.0.0' compile 'com.android.support:design:25.0.0' - compile 'agency.tango.android:material-intro-screen:0.0.3' compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' compile 'me.dm7.barcodescanner:zxing:1.9' compile 'com.android.support:cardview-v7:25.0.0' @@ -39,5 +38,6 @@ dependencies { compile 'com.yarolegovich:lovely-dialog:1.0.4' compile 'com.mattprecious.swirl:swirl:1.0.0' compile 'com.madgag.spongycastle:core:1.56.0.0' + compile 'com.github.apl-devs:appintro:v4.2.2' testCompile 'junit:junit:4.12' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 55887724..c769b8c0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -30,14 +30,11 @@ - - diff --git a/app/src/main/java/me/impy/aegis/CustomAuthenticatedSlide.java b/app/src/main/java/me/impy/aegis/CustomAuthenticatedSlide.java index b485b82a..5e2b52cf 100644 --- a/app/src/main/java/me/impy/aegis/CustomAuthenticatedSlide.java +++ b/app/src/main/java/me/impy/aegis/CustomAuthenticatedSlide.java @@ -2,12 +2,17 @@ package me.impy.aegis; import android.content.Intent; import android.os.Bundle; +import android.support.design.widget.Snackbar; +import android.support.v4.app.Fragment; import android.text.Editable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; +import com.github.paolorotolo.appintro.ISlidePolicy; +import com.github.paolorotolo.appintro.ISlideSelectionListener; + import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; @@ -17,80 +22,29 @@ import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; -import agency.tango.materialintroscreen.SlideFragment; import me.impy.aegis.crypto.CryptoUtils; import me.impy.aegis.crypto.slots.PasswordSlot; import me.impy.aegis.crypto.slots.Slot; -public class CustomAuthenticatedSlide extends SlideFragment { +public class CustomAuthenticatedSlide extends Fragment implements ISlidePolicy, ISlideSelectionListener { private int cryptType; private EditText textPassword; private EditText textPasswordConfirm; + private int bgColor; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_authenticated_slide, container, false); + final View view = inflater.inflate(R.layout.fragment_authenticated_slide, container, false); textPassword = (EditText) view.findViewById(R.id.text_password); textPasswordConfirm = (EditText) view.findViewById(R.id.text_password_confirm); + view.findViewById(R.id.main).setBackgroundColor(bgColor); return view; } - @Override - public void setUserVisibleHint(boolean isVisibleToUser) { - super.setUserVisibleHint(isVisibleToUser); - - if (!isVisibleToUser) { - return; - } - - Intent intent = getActivity().getIntent(); - cryptType = intent.getIntExtra("cryptType", 1337); - - switch(cryptType) { - case CustomAuthenticationSlide.CRYPT_TYPE_NONE: - break; - case CustomAuthenticationSlide.CRYPT_TYPE_PASS: - break; - case CustomAuthenticationSlide.CRYPT_TYPE_FINGER: - break; - default: - throw new RuntimeException(); - } - } - - @Override - public int backgroundColor() { - return R.color.colorHeaderSuccess; - } - - @Override + /*@Override public int buttonsColor() { return R.color.colorAccent; - } - - @Override - public boolean canMoveFurther() { - switch(cryptType) { - case CustomAuthenticationSlide.CRYPT_TYPE_NONE: - return true; - case CustomAuthenticationSlide.CRYPT_TYPE_PASS: - char[] password = getEditTextChars(textPassword); - char[] passwordConfirm = getEditTextChars(textPasswordConfirm); - boolean equal = password.length != 0 && Arrays.equals(password, passwordConfirm); - CryptoUtils.zero(password); - CryptoUtils.zero(passwordConfirm); - return equal; - case CustomAuthenticationSlide.CRYPT_TYPE_FINGER: - return false; - default: - throw new RuntimeException(); - } - } - - @Override - public String cantMoveFurtherErrorMessage() { - return "Passwords should be equal and non-empty"; - } + }*/ public int getCryptType() { return cryptType; @@ -121,4 +75,57 @@ public class CustomAuthenticatedSlide extends SlideFragment { editable.getChars(0, editable.length(), chars, 0); return chars; } + + @Override + public void onSlideSelected() { + Intent intent = getActivity().getIntent(); + cryptType = intent.getIntExtra("cryptType", 1337); + + switch(cryptType) { + case CustomAuthenticationSlide.CRYPT_TYPE_NONE: + break; + case CustomAuthenticationSlide.CRYPT_TYPE_PASS: + break; + case CustomAuthenticationSlide.CRYPT_TYPE_FINGER: + break; + default: + throw new RuntimeException(); + } + } + + public void setBgColor(int color) { + bgColor = color; + } + + @Override + public void onSlideDeselected() { + } + + @Override + public boolean isPolicyRespected() { + switch(cryptType) { + case CustomAuthenticationSlide.CRYPT_TYPE_NONE: + return true; + case CustomAuthenticationSlide.CRYPT_TYPE_PASS: + char[] password = getEditTextChars(textPassword); + char[] passwordConfirm = getEditTextChars(textPasswordConfirm); + boolean equal = password.length != 0 && Arrays.equals(password, passwordConfirm); + CryptoUtils.zero(password); + CryptoUtils.zero(passwordConfirm); + return equal; + case CustomAuthenticationSlide.CRYPT_TYPE_FINGER: + return false; + default: + throw new RuntimeException(); + } + } + + @Override + public void onUserIllegallyRequestedNextPage() { + View view = getView(); + if (view != null) { + Snackbar snackbar = Snackbar.make(getView(), "Passwords should be equal and non-empty", Snackbar.LENGTH_LONG); + snackbar.show(); + } + } } diff --git a/app/src/main/java/me/impy/aegis/CustomAuthenticationSlide.java b/app/src/main/java/me/impy/aegis/CustomAuthenticationSlide.java index f6c9e03e..ca2599ad 100644 --- a/app/src/main/java/me/impy/aegis/CustomAuthenticationSlide.java +++ b/app/src/main/java/me/impy/aegis/CustomAuthenticationSlide.java @@ -7,24 +7,28 @@ import android.content.pm.PackageManager; import android.hardware.fingerprint.FingerprintManager; import android.os.Build; import android.os.Bundle; -import android.support.annotation.IdRes; +import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; +import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.TextView; import android.widget.Toast; -import agency.tango.materialintroscreen.SlideFragment; +import com.github.paolorotolo.appintro.ISlidePolicy; -public class CustomAuthenticationSlide extends SlideFragment { - public static final int CRYPT_TYPE_NONE = 0; - public static final int CRYPT_TYPE_PASS = 1; - public static final int CRYPT_TYPE_FINGER = 2; +public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy { + public static final int CRYPT_TYPE_INVALID = 0; + public static final int CRYPT_TYPE_NONE = 1; + public static final int CRYPT_TYPE_PASS = 2; + public static final int CRYPT_TYPE_FINGER = 3; private RadioGroup buttonGroup; + private int bgColor; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -34,7 +38,7 @@ public class CustomAuthenticationSlide extends SlideFragment { buttonGroup = (RadioGroup) view.findViewById(R.id.rg_authenticationMethod); buttonGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override - public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) { + public void onCheckedChanged(RadioGroup group, int checkedId) { if (checkedId == -1) { return; } @@ -72,26 +76,27 @@ public class CustomAuthenticationSlide extends SlideFragment { } } + view.findViewById(R.id.main).setBackgroundColor(bgColor); return view; } - @Override - public int backgroundColor() { - return R.color.colorHeaderSuccess; + public void setBgColor(int color) { + bgColor = color; } @Override - public int buttonsColor() { - return R.color.colorAccent; - } - - @Override - public boolean canMoveFurther() { + public boolean isPolicyRespected() { return buttonGroup.getCheckedRadioButtonId() != -1; } @Override - public String cantMoveFurtherErrorMessage() { - return "Please select an authentication method"; + public void onUserIllegallyRequestedNextPage() { + Snackbar snackbar = Snackbar.make(getView(), "Please select an authentication method", Snackbar.LENGTH_LONG); + snackbar.show(); } + + /*@Override + public int buttonsColor() { + return R.color.colorAccent; + }*/ } diff --git a/app/src/main/java/me/impy/aegis/EditProfileBottomSheetdialog.java b/app/src/main/java/me/impy/aegis/EditProfileBottomSheetdialog.java index 1326a605..2599d0ef 100644 --- a/app/src/main/java/me/impy/aegis/EditProfileBottomSheetdialog.java +++ b/app/src/main/java/me/impy/aegis/EditProfileBottomSheetdialog.java @@ -1,9 +1,5 @@ package me.impy.aegis; -import android.app.Dialog; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.BottomSheetDialogFragment; @@ -11,7 +7,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; -import android.widget.Toast; public class EditProfileBottomSheetdialog extends BottomSheetDialogFragment { LinearLayout copyLayout; diff --git a/app/src/main/java/me/impy/aegis/IntroActivity.java b/app/src/main/java/me/impy/aegis/IntroActivity.java index 88d3be69..79a15193 100644 --- a/app/src/main/java/me/impy/aegis/IntroActivity.java +++ b/app/src/main/java/me/impy/aegis/IntroActivity.java @@ -5,17 +5,14 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.view.View; +import android.support.v4.app.Fragment; -import java.lang.reflect.UndeclaredThrowableException; -import java.security.NoSuchAlgorithmException; +import com.github.paolorotolo.appintro.AppIntro; +import com.github.paolorotolo.appintro.AppIntroFragment; +import com.github.paolorotolo.appintro.model.SliderPage; import javax.crypto.Cipher; -import agency.tango.materialintroscreen.MaterialIntroActivity; -import agency.tango.materialintroscreen.MessageButtonBehaviour; -import agency.tango.materialintroscreen.SlideFragmentBuilder; import me.impy.aegis.crypto.CryptResult; import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.crypto.slots.PasswordSlot; @@ -23,63 +20,77 @@ import me.impy.aegis.crypto.slots.SlotCollection; import me.impy.aegis.db.Database; import me.impy.aegis.db.DatabaseFile; -public class IntroActivity extends MaterialIntroActivity { +public class IntroActivity extends AppIntro { public static final int RESULT_OK = 0; public static final int RESULT_EXCEPTION = 1; private CustomAuthenticatedSlide authenticatedSlide; + private CustomAuthenticationSlide authenticationSlide; + private Fragment endSlide; @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { + protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - hideBackButton(); + showSkipButton(false); + //showPagerIndicator(false); + setGoBackLock(true); + // TODO: remove this once github.com/apl-devs/AppIntro/issues/347 is fixed + setSwipeLock(true); - addSlide(new SlideFragmentBuilder() - .backgroundColor(R.color.colorPrimary) - .buttonsColor(R.color.colorAccent) - .image(R.drawable.intro_shield) - .title("Welcome") - .description("Aegis is a brand new open source(!) authenticator app which generates tokens for your accounts.") - .build()); + SliderPage homeSliderPage = new SliderPage(); + homeSliderPage.setTitle("Welcome"); + homeSliderPage.setDescription("Aegis is a secure, free and open source 2FA app"); + homeSliderPage.setImageDrawable(R.drawable.intro_shield); + homeSliderPage.setBgColor(getResources().getColor(R.color.colorPrimary)); + addSlide(AppIntroFragment.newInstance(homeSliderPage)); - addSlide(new SlideFragmentBuilder() - .backgroundColor(R.color.colorAccent) - .buttonsColor(R.color.colorPrimary) - .neededPermissions(new String[]{Manifest.permission.CAMERA}) - .image(R.drawable.intro_scanner) - .title("Permissions") - .description("Aegis needs permission to your camera in order to function properly. This is needed to scan QR codes.") - .build(), - new MessageButtonBehaviour(new View.OnClickListener() { - @Override - public void onClick(View v) { - - } - }, "Permission granted")); - - addSlide(new CustomAuthenticationSlide()); + SliderPage permSliderPage = new SliderPage(); + permSliderPage.setTitle("Permissions"); + permSliderPage.setDescription("Aegis needs permission to use your camera in order to scan QR codes."); + permSliderPage.setImageDrawable(R.drawable.intro_scanner); + permSliderPage.setBgColor(getResources().getColor(R.color.colorAccent)); + addSlide(AppIntroFragment.newInstance(permSliderPage)); + askForPermissions(new String[]{Manifest.permission.CAMERA}, 2); + authenticationSlide = new CustomAuthenticationSlide(); + authenticationSlide.setBgColor(getResources().getColor(R.color.colorHeaderSuccess)); + addSlide(authenticationSlide); authenticatedSlide = new CustomAuthenticatedSlide(); + authenticatedSlide.setBgColor(getResources().getColor(R.color.colorPrimary)); addSlide(authenticatedSlide); - addSlide(new SlideFragmentBuilder() - .backgroundColor(R.color.colorPrimary) - .buttonsColor(R.color.colorAccent) - .image(R.drawable.intro_shield) - .title("All done!") - .description("Aegis has been set up and is ready to go.") - .build()); + SliderPage endSliderPage = new SliderPage(); + endSliderPage.setTitle("All done!"); + endSliderPage.setDescription("Aegis has been set up and is ready to go."); + endSliderPage.setImageDrawable(R.drawable.intro_shield); + endSliderPage.setBgColor(getResources().getColor(R.color.colorPrimary)); + endSlide = AppIntroFragment.newInstance(endSliderPage); + addSlide(endSlide); } private void setException(Exception e) { Intent result = new Intent(); result.putExtra("exception", e); setResult(RESULT_EXCEPTION, result); + finish(); } @Override - public void onFinish() { - super.onFinish(); + public void onSlideChanged(Fragment oldFragment, Fragment newFragment) { + // skip to the last slide if no encryption will be used + if (oldFragment == authenticationSlide && newFragment != endSlide) { + Intent intent = getIntent(); + int cryptType = intent.getIntExtra("cryptType", CustomAuthenticationSlide.CRYPT_TYPE_INVALID); + if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_NONE) { + // TODO: no magic indices + getPager().setCurrentItem(5); + } + } + } + + @Override + public void onDonePressed(Fragment currentFragment) { + super.onDonePressed(currentFragment); // create the database and database file Database database = new Database(); @@ -142,5 +153,6 @@ public class IntroActivity extends MaterialIntroActivity { // TODO: show the intro if we can't find any database files SharedPreferences prefs = this.getSharedPreferences("me.impy.aegis", Context.MODE_PRIVATE); prefs.edit().putBoolean("passedIntro", true).apply(); + finish(); } } diff --git a/app/src/main/java/me/impy/aegis/MainActivity.java b/app/src/main/java/me/impy/aegis/MainActivity.java index 3871ab36..62925152 100644 --- a/app/src/main/java/me/impy/aegis/MainActivity.java +++ b/app/src/main/java/me/impy/aegis/MainActivity.java @@ -409,9 +409,4 @@ public class MainActivity extends AppCompatActivity { e.printStackTrace(); } } - - private boolean causeIsKeyUserNotAuthenticated(Exception e) { - // TODO: is there a way to catch "Key user not authenticated" specifically aside from checking the exception message? - return e.getCause().getMessage().equals("Key user not authenticated"); - } } diff --git a/app/src/main/java/me/impy/aegis/SetPasswordActivity.java b/app/src/main/java/me/impy/aegis/SetPasswordActivity.java deleted file mode 100644 index 879a2dea..00000000 --- a/app/src/main/java/me/impy/aegis/SetPasswordActivity.java +++ /dev/null @@ -1,13 +0,0 @@ -package me.impy.aegis; - -import android.support.v7.app.AppCompatActivity; -import android.os.Bundle; - -public class SetPasswordActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_set_password); - } -} diff --git a/app/src/main/res/layout/activity_intro.xml b/app/src/main/res/layout/activity_intro.xml index c7b5f978..3882726a 100644 --- a/app/src/main/res/layout/activity_intro.xml +++ b/app/src/main/res/layout/activity_intro.xml @@ -1,35 +1,10 @@ - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_set_password.xml b/app/src/main/res/layout/activity_set_password.xml deleted file mode 100644 index 11394179..00000000 --- a/app/src/main/res/layout/activity_set_password.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - -