From d263cf583a118174658789df77c619761cce6c89 Mon Sep 17 00:00:00 2001 From: jayba Date: Tue, 3 Apr 2018 04:40:52 +0800 Subject: [PATCH 01/27] sdk update --- build.gradle | 2 +- lib/.gitignore | 1 - lib/proguard-rules.pro | 17 - .../lollipin/lib/ApplicationTest.java | 13 - lib/src/main/AndroidManifest.xml | 10 - .../omadahealth/lollipin/lib/PinActivity.java | 86 ---- .../lollipin/lib/PinCompatActivity.java | 85 --- .../lollipin/lib/PinFragmentActivity.java | 85 --- .../lollipin/lib/encryption/Encryptor.java | 86 ---- .../lollipin/lib/enums/Algorithm.java | 28 - .../lib/enums/KeyboardButtonEnum.java | 33 -- .../KeyboardButtonClickedListener.java | 27 - .../lib/interfaces/LifeCycleInterface.java | 28 - .../lollipin/lib/managers/AppLock.java | 205 -------- .../lib/managers/AppLockActivity.java | 484 ------------------ .../lollipin/lib/managers/AppLockImpl.java | 421 --------------- .../lib/managers/FingerprintUiHelper.java | 315 ------------ .../lollipin/lib/managers/LockManager.java | 83 --- .../lib/views/KeyboardButtonView.java | 105 ---- .../lollipin/lib/views/KeyboardView.java | 115 ----- .../lollipin/lib/views/PinCodeRoundView.java | 144 ------ .../lollipin/lib/views/PinCodeView.java | 39 -- .../lollipin/lib/views/SquareImageView.java | 33 -- lib/src/main/res/anim/cycle5.xml | 3 - lib/src/main/res/anim/nothing.xml | 5 - lib/src/main/res/anim/shake.xml | 6 - lib/src/main/res/anim/slide_down.xml | 6 - .../ic_backspace_grey600_24dp.png | Bin 441 -> 0 bytes lib/src/main/res/drawable-hdpi/ic_fp_40px.png | Bin 7011 -> 0 bytes lib/src/main/res/drawable-hdpi/tile.9.png | Bin 196 -> 0 bytes .../ic_backspace_grey600_24dp.png | Bin 333 -> 0 bytes lib/src/main/res/drawable-mdpi/ic_fp_40px.png | Bin 4001 -> 0 bytes .../ic_backspace_grey600_24dp.png | Bin 529 -> 0 bytes .../main/res/drawable-xhdpi/ic_fp_40px.png | Bin 10524 -> 0 bytes .../ic_backspace_grey600_24dp.png | Bin 699 -> 0 bytes .../main/res/drawable-xxhdpi/ic_fp_40px.png | Bin 18565 -> 0 bytes .../ic_backspace_grey600_24dp.png | Bin 942 -> 0 bytes .../main/res/drawable-xxxhdpi/ic_fp_40px.png | Bin 16535 -> 0 bytes .../res/drawable/ic_fingerprint_error.xml | 28 - .../res/drawable/ic_fingerprint_success.xml | 28 - .../res/drawable/pin_code_round_empty.xml | 15 - .../main/res/drawable/pin_code_round_full.xml | 15 - lib/src/main/res/layout/activity_pin_code.xml | 97 ---- lib/src/main/res/layout/view_keyboard.xml | 127 ----- .../main/res/layout/view_keyboard_button.xml | 31 -- lib/src/main/res/layout/view_round.xml | 11 - .../main/res/layout/view_round_pin_code.xml | 9 - lib/src/main/res/values-id/strings.xml | 14 - lib/src/main/res/values-ko/strings.xml | 14 - lib/src/main/res/values-pt/strings.xml | 12 - lib/src/main/res/values-ru/strings.xml | 12 - lib/src/main/res/values/attrs.xml | 18 - lib/src/main/res/values/colors.xml | 13 - lib/src/main/res/values/dimens.xml | 21 - lib/src/main/res/values/integers.xml | 4 - lib/src/main/res/values/strings.xml | 34 -- {lib => lollipin}/build.gradle | 12 +- 57 files changed, 7 insertions(+), 2973 deletions(-) delete mode 100644 lib/.gitignore delete mode 100644 lib/proguard-rules.pro delete mode 100644 lib/src/androidTest/java/com/github/omadahealth/lollipin/lib/ApplicationTest.java delete mode 100644 lib/src/main/AndroidManifest.xml delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/PinActivity.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/PinCompatActivity.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/PinFragmentActivity.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/encryption/Encryptor.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/enums/Algorithm.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/enums/KeyboardButtonEnum.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/KeyboardButtonClickedListener.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/LifeCycleInterface.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLock.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockImpl.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/LockManager.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardButtonView.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardView.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeRoundView.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeView.java delete mode 100644 lib/src/main/java/com/github/omadahealth/lollipin/lib/views/SquareImageView.java delete mode 100644 lib/src/main/res/anim/cycle5.xml delete mode 100644 lib/src/main/res/anim/nothing.xml delete mode 100644 lib/src/main/res/anim/shake.xml delete mode 100644 lib/src/main/res/anim/slide_down.xml delete mode 100755 lib/src/main/res/drawable-hdpi/ic_backspace_grey600_24dp.png delete mode 100644 lib/src/main/res/drawable-hdpi/ic_fp_40px.png delete mode 100644 lib/src/main/res/drawable-hdpi/tile.9.png delete mode 100755 lib/src/main/res/drawable-mdpi/ic_backspace_grey600_24dp.png delete mode 100644 lib/src/main/res/drawable-mdpi/ic_fp_40px.png delete mode 100755 lib/src/main/res/drawable-xhdpi/ic_backspace_grey600_24dp.png delete mode 100644 lib/src/main/res/drawable-xhdpi/ic_fp_40px.png delete mode 100755 lib/src/main/res/drawable-xxhdpi/ic_backspace_grey600_24dp.png delete mode 100644 lib/src/main/res/drawable-xxhdpi/ic_fp_40px.png delete mode 100755 lib/src/main/res/drawable-xxxhdpi/ic_backspace_grey600_24dp.png delete mode 100644 lib/src/main/res/drawable-xxxhdpi/ic_fp_40px.png delete mode 100644 lib/src/main/res/drawable/ic_fingerprint_error.xml delete mode 100644 lib/src/main/res/drawable/ic_fingerprint_success.xml delete mode 100644 lib/src/main/res/drawable/pin_code_round_empty.xml delete mode 100644 lib/src/main/res/drawable/pin_code_round_full.xml delete mode 100644 lib/src/main/res/layout/activity_pin_code.xml delete mode 100644 lib/src/main/res/layout/view_keyboard.xml delete mode 100644 lib/src/main/res/layout/view_keyboard_button.xml delete mode 100644 lib/src/main/res/layout/view_round.xml delete mode 100644 lib/src/main/res/layout/view_round_pin_code.xml delete mode 100644 lib/src/main/res/values-id/strings.xml delete mode 100644 lib/src/main/res/values-ko/strings.xml delete mode 100644 lib/src/main/res/values-pt/strings.xml delete mode 100644 lib/src/main/res/values-ru/strings.xml delete mode 100644 lib/src/main/res/values/attrs.xml delete mode 100644 lib/src/main/res/values/colors.xml delete mode 100644 lib/src/main/res/values/dimens.xml delete mode 100644 lib/src/main/res/values/integers.xml delete mode 100644 lib/src/main/res/values/strings.xml rename {lib => lollipin}/build.gradle (85%) diff --git a/build.gradle b/build.gradle index dbe7511e..5cd0458e 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:3.1.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/lib/.gitignore b/lib/.gitignore deleted file mode 100644 index 796b96d1..00000000 --- a/lib/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/lib/proguard-rules.pro b/lib/proguard-rules.pro deleted file mode 100644 index baf289b8..00000000 --- a/lib/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/stoyan/android_sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/lib/src/androidTest/java/com/github/omadahealth/lollipin/lib/ApplicationTest.java b/lib/src/androidTest/java/com/github/omadahealth/lollipin/lib/ApplicationTest.java deleted file mode 100644 index 9d4f292c..00000000 --- a/lib/src/androidTest/java/com/github/omadahealth/lollipin/lib/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.omadahealth.lollipin.lib; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/lib/src/main/AndroidManifest.xml b/lib/src/main/AndroidManifest.xml deleted file mode 100644 index 836fdeef..00000000 --- a/lib/src/main/AndroidManifest.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/PinActivity.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/PinActivity.java deleted file mode 100644 index 11ed7d6c..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/PinActivity.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.github.omadahealth.lollipin.lib; - - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; - -import com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface; -import com.github.omadahealth.lollipin.lib.managers.AppLockActivity; - -/** - * Created by stoyan and olivier on 1/12/15. - * You must extend this Activity in order to support this library. - * Then to enable PinCode blocking, you must call - * {@link com.github.omadahealth.lollipin.lib.managers.LockManager#enableAppLock(android.content.Context, Class)} - */ -public class PinActivity extends Activity { - private static LifeCycleInterface mLifeCycleListener; - private final BroadcastReceiver mPinCancelledReceiver; - - public PinActivity() { - super(); - mPinCancelledReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - finish(); - } - }; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - IntentFilter filter = new IntentFilter(AppLockActivity.ACTION_CANCEL); - LocalBroadcastManager.getInstance(this).registerReceiver(mPinCancelledReceiver, filter); - } - - @Override - public void onUserInteraction() { - if (mLifeCycleListener != null){ - mLifeCycleListener.onActivityUserInteraction(PinActivity.this); - } - super.onUserInteraction(); - } - - @Override - protected void onResume() { - if (mLifeCycleListener != null) { - mLifeCycleListener.onActivityResumed(PinActivity.this); - } - super.onResume(); - } - - @Override - protected void onPause() { - if (mLifeCycleListener != null) { - mLifeCycleListener.onActivityPaused(PinActivity.this); - } - super.onPause(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - LocalBroadcastManager.getInstance(this).unregisterReceiver(mPinCancelledReceiver); - } - - public static void setListener(LifeCycleInterface listener) { - if (mLifeCycleListener != null) { - mLifeCycleListener = null; - } - mLifeCycleListener = listener; - } - - public static void clearListeners() { - mLifeCycleListener = null; - } - - public static boolean hasListeners() { - return (mLifeCycleListener != null); - } -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/PinCompatActivity.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/PinCompatActivity.java deleted file mode 100644 index 15c06d51..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/PinCompatActivity.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.github.omadahealth.lollipin.lib; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; -import android.support.v7.app.AppCompatActivity; - -import com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface; -import com.github.omadahealth.lollipin.lib.managers.AppLockActivity; - -/** - * Created by callmepeanut on 16-1-14. - * You must extend this Activity in order to support this library. - * Then to enable PinCode blocking, you must call - * {@link com.github.omadahealth.lollipin.lib.managers.LockManager#enableAppLock(android.content.Context, Class)} - */ -public class PinCompatActivity extends AppCompatActivity { - private static LifeCycleInterface mLifeCycleListener; - private final BroadcastReceiver mPinCancelledReceiver; - - public PinCompatActivity() { - super(); - mPinCancelledReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - finish(); - } - }; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - IntentFilter filter = new IntentFilter(AppLockActivity.ACTION_CANCEL); - LocalBroadcastManager.getInstance(this).registerReceiver(mPinCancelledReceiver, filter); - } - - @Override - protected void onResume() { - if (mLifeCycleListener != null) { - mLifeCycleListener.onActivityResumed(PinCompatActivity.this); - } - super.onResume(); - } - - @Override - public void onUserInteraction() { - if (mLifeCycleListener != null){ - mLifeCycleListener.onActivityUserInteraction(PinCompatActivity.this); - } - super.onUserInteraction(); - } - - @Override - protected void onPause() { - if (mLifeCycleListener != null) { - mLifeCycleListener.onActivityPaused(PinCompatActivity.this); - } - super.onPause(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - LocalBroadcastManager.getInstance(this).unregisterReceiver(mPinCancelledReceiver); - } - - public static void setListener(LifeCycleInterface listener) { - if (mLifeCycleListener != null) { - mLifeCycleListener = null; - } - mLifeCycleListener = listener; - } - - public static void clearListeners() { - mLifeCycleListener = null; - } - - public static boolean hasListeners() { - return (mLifeCycleListener != null); - } -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/PinFragmentActivity.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/PinFragmentActivity.java deleted file mode 100644 index 78c49479..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/PinFragmentActivity.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.github.omadahealth.lollipin.lib; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.support.v4.app.FragmentActivity; -import android.support.v4.content.LocalBroadcastManager; - -import com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface; -import com.github.omadahealth.lollipin.lib.managers.AppLockActivity; - -/** - * Created by stoyan and olivier on 1/12/15. - * You must extend this Activity in order to support this library. - * Then to enable PinCode blocking, you must call - * {@link com.github.omadahealth.lollipin.lib.managers.LockManager#enableAppLock(android.content.Context, Class)} - */ -public class PinFragmentActivity extends FragmentActivity { - private static LifeCycleInterface mLifeCycleListener; - private final BroadcastReceiver mPinCancelledReceiver; - - public PinFragmentActivity() { - super(); - mPinCancelledReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - finish(); - } - }; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - IntentFilter filter = new IntentFilter(AppLockActivity.ACTION_CANCEL); - LocalBroadcastManager.getInstance(this).registerReceiver(mPinCancelledReceiver, filter); - } - - @Override - protected void onResume() { - if (mLifeCycleListener != null) { - mLifeCycleListener.onActivityResumed(PinFragmentActivity.this); - } - super.onResume(); - } - - @Override - public void onUserInteraction() { - if (mLifeCycleListener != null){ - mLifeCycleListener.onActivityUserInteraction(PinFragmentActivity.this); - } - super.onUserInteraction(); - } - - @Override - protected void onPause() { - if (mLifeCycleListener != null) { - mLifeCycleListener.onActivityPaused(PinFragmentActivity.this); - } - super.onPause(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - LocalBroadcastManager.getInstance(this).unregisterReceiver(mPinCancelledReceiver); - } - - public static void setListener(LifeCycleInterface listener) { - if (mLifeCycleListener != null) { - mLifeCycleListener = null; - } - mLifeCycleListener = listener; - } - - public static void clearListeners() { - mLifeCycleListener = null; - } - - public static boolean hasListeners() { - return (mLifeCycleListener != null); - } -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/encryption/Encryptor.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/encryption/Encryptor.java deleted file mode 100644 index b04acf23..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/encryption/Encryptor.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.github.omadahealth.lollipin.lib.encryption; - -import android.text.TextUtils; - -import com.github.omadahealth.lollipin.lib.enums.Algorithm; - -import java.security.MessageDigest; -import java.util.Locale; - -/** - * Used by {@link com.github.omadahealth.lollipin.lib.managers.AppLockImpl} to get the SHA1 - * of the 4-digit password. - */ -public class Encryptor { - - /** - * Convert a chain of bytes into a {@link java.lang.String} - * - * @param bytes The chain of bytes - * @return The converted String - */ - private static String bytes2Hex(byte[] bytes) { - String hs = ""; - String stmp = ""; - for (int n = 0; n < bytes.length; n++) { - stmp = (Integer.toHexString(bytes[n] & 0XFF)); - if (stmp.length() == 1) { - hs += "0" + stmp; - } else { - hs += stmp; - } - } - return hs.toLowerCase(Locale.ENGLISH); - } - - /** - * Allows to get the SHA of a {@link java.lang.String} using {@link java.security.MessageDigest} - * if device does not support sha-256, fall back to sha-1 instead - */ - public static String getSHA(String text, Algorithm algorithm) { - String sha = ""; - if (TextUtils.isEmpty(text)) { - return sha; - } - - MessageDigest shaDigest = getShaDigest(algorithm); - - if (shaDigest != null) { - byte[] textBytes = text.getBytes(); - shaDigest.update(textBytes, 0, text.length()); - byte[] shahash = shaDigest.digest(); - return bytes2Hex(shahash); - } - - return null; - } - - /** - * Gets the default {@link MessageDigest} to use. - * Select {@link Algorithm#SHA256} in {@link com.github.omadahealth.lollipin.lib.managers.AppLockImpl#setPasscode(String)} - * but can be {@link Algorithm#SHA1} for older versions. - * - * @param algorithm The {@link Algorithm} to use - */ - private static MessageDigest getShaDigest(Algorithm algorithm) { - switch (algorithm) { - case SHA256: - try { - return MessageDigest.getInstance("SHA-256"); - } catch (Exception e) { - try { - return MessageDigest.getInstance("SHA-1"); - } catch (Exception e2) { - return null; - } - } - case SHA1: - default: - try { - return MessageDigest.getInstance("SHA-1"); - } catch (Exception e2) { - return null; - } - } - } -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/enums/Algorithm.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/enums/Algorithm.java deleted file mode 100644 index 2525e9f9..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/enums/Algorithm.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.github.omadahealth.lollipin.lib.enums; - -/** - * Created by olivier.goutay on 4/15/16. - */ -public enum Algorithm { - - SHA1("1"), SHA256("2"); - - private String mValue; - - Algorithm(String value) { - this.mValue = value; - } - - public String getValue() { - return mValue; - } - - public static Algorithm getFromText(String text) { - for (Algorithm algorithm : Algorithm.values()) { - if (algorithm.mValue.equals(text)) { - return algorithm; - } - } - return SHA1; - } -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/enums/KeyboardButtonEnum.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/enums/KeyboardButtonEnum.java deleted file mode 100644 index 6bb1bf0c..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/enums/KeyboardButtonEnum.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.github.omadahealth.lollipin.lib.enums; - -/** - * Created by stoyan and oliviergoutay on 1/13/15. - */ -public enum KeyboardButtonEnum { - - BUTTON_0(0), - BUTTON_1(1), - BUTTON_2(2), - BUTTON_3(3), - BUTTON_4(4), - BUTTON_5(5), - BUTTON_6(6), - BUTTON_7(7), - BUTTON_8(8), - BUTTON_9(9), - BUTTON_CLEAR(-1); - - private int mButtonValue; - - KeyboardButtonEnum(int value) { - this.mButtonValue = value; - } - - /** - * Get the button value (numeric) - * @return 0-9 values for digits, -1 for clear button - */ - public int getButtonValue() { - return mButtonValue; - } -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/KeyboardButtonClickedListener.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/KeyboardButtonClickedListener.java deleted file mode 100644 index 73d7828a..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/KeyboardButtonClickedListener.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.omadahealth.lollipin.lib.interfaces; - -import com.github.omadahealth.lollipin.lib.enums.KeyboardButtonEnum; - -/** - * Created by stoyan and oliviergoutay on 1/13/15. - * The {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} will implement - * this in order to receive events from {@link com.github.omadahealth.lollipin.lib.views.KeyboardButtonView} - * and {@link com.github.omadahealth.lollipin.lib.views.KeyboardView} - */ -public interface KeyboardButtonClickedListener { - - /** - * Receive the click of a button, just after a {@link android.view.View.OnClickListener} has fired. - * Called before {@link #onRippleAnimationEnd()}. - * @param keyboardButtonEnum The organized enum of the clicked button - */ - public void onKeyboardClick(KeyboardButtonEnum keyboardButtonEnum); - - /** - * Receive the end of a {@link com.andexert.library.RippleView} animation using a - * {@link com.andexert.library.RippleAnimationListener} to determine the end. - * Called after {@link #onKeyboardClick(com.github.omadahealth.lollipin.lib.enums.KeyboardButtonEnum)}. - */ - public void onRippleAnimationEnd(); - -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/LifeCycleInterface.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/LifeCycleInterface.java deleted file mode 100644 index 8ccbb025..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/LifeCycleInterface.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.github.omadahealth.lollipin.lib.interfaces; - -import android.app.Activity; - -/** - * Created by stoyan on 1/12/15. - * Allows to follow the LifeCycle of the {@link com.github.omadahealth.lollipin.lib.PinActivity} - * Implemented by {@link com.github.omadahealth.lollipin.lib.managers.AppLockImpl} in order to - * determine when the app was launched for the last time and when to launch the - * {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} - */ -public interface LifeCycleInterface { - - /** - * Called in {@link android.app.Activity#onResume()} - */ - public void onActivityResumed(Activity activity); - - /** - * Called in {@link Activity#onUserInteraction()} - */ - public void onActivityUserInteraction(Activity activity); - - /** - * Called in {@link android.app.Activity#onPause()} - */ - public void onActivityPaused(Activity activity); -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLock.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLock.java deleted file mode 100644 index d2fcc686..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLock.java +++ /dev/null @@ -1,205 +0,0 @@ -package com.github.omadahealth.lollipin.lib.managers; - -import android.app.Activity; - -import java.util.HashSet; - -public abstract class AppLock { - /** - * ENABLE_PINLOCK type, uses at firt to define the password - */ - public static final int ENABLE_PINLOCK = 0; - /** - * DISABLE_PINLOCK type, uses to disable the system by asking the current password - */ - public static final int DISABLE_PINLOCK = 1; - /** - * CHANGE_PIN type, uses to change the current password - */ - public static final int CHANGE_PIN = 2; - /** - * CONFIRM_PIN type, used to confirm the new password - */ - public static final int CONFIRM_PIN = 3; - /** - * UNLOCK_PIN type, uses to ask the password to the user, in order to unlock the app - */ - public static final int UNLOCK_PIN = 4; - - /** - * LOGO_ID_NONE used to denote when a user has not set a logoId using {@link #setLogoId(int)} - */ - public static final int LOGO_ID_NONE = -1; - - /** - * EXTRA_TYPE, uses to pass to the {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} - * to determine in which type it musts be started. - */ - public static final String EXTRA_TYPE = "type"; - - /** - * DEFAULT_TIMEOUT, define the default timeout returned by {@link #getTimeout()}. - * If you want to modify it, you can call {@link #setTimeout(long)}. Will be stored using - * {@link android.content.SharedPreferences} - */ - public static final long DEFAULT_TIMEOUT = 1000 * 10; // 10sec - - /** - * A {@link java.util.HashSet} of {@link java.lang.String} which are the classes we don't want to - * take into account for the {@link com.github.omadahealth.lollipin.lib.PinActivity}. These activities - * will not log the last opened time, will not launch the - * {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} etc... - */ - protected HashSet mIgnoredActivities; - - public AppLock() { - mIgnoredActivities = new HashSet(); - } - - /** - * Add an ignored activity to the {@link java.util.HashSet} - */ - public void addIgnoredActivity(Class clazz) { - String clazzName = clazz.getName(); - this.mIgnoredActivities.add(clazzName); - } - - /** - * Remove an ignored activity to the {@link java.util.HashSet} - */ - public void removeIgnoredActivity(Class clazz) { - String clazzName = clazz.getName(); - this.mIgnoredActivities.remove(clazzName); - } - - /** - * Get the timeout used in {@link #shouldLockSceen(android.app.Activity)} - */ - public abstract long getTimeout(); - - /** - * Set the timeout used in {@link #shouldLockSceen(android.app.Activity)} - */ - public abstract void setTimeout(long timeout); - - /** - * Get logo resource id used by {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} - */ - public abstract int getLogoId(); - - /** - * Set logo resource id used by {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} - */ - public abstract void setLogoId(int logoId); - - /** - * Get the forgot option used by {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} - */ - public abstract boolean shouldShowForgot(int appLockType); - - /** - * Set the forgot option used by {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} - */ - public abstract void setShouldShowForgot(boolean showForgot); - - /** - * Get whether the user backed out of the {@link AppLockActivity} previously - */ - public abstract boolean pinChallengeCancelled(); - - /** - * Set whether the user backed out of the {@link AppLockActivity} - */ - public abstract void setPinChallengeCancelled(boolean cancelled); - - - /** - * Get the only background timeout option used to determine if the time - * spent in the activity must NOT be taken into account while calculating the timeout. - */ - public abstract boolean onlyBackgroundTimeout(); - - /** - * Set whether the time spent on the activity must NOT be taken into account when calculating timeout. - */ - public abstract void setOnlyBackgroundTimeout(boolean onlyBackgroundTimeout); - - /** - * Enable the {@link com.github.omadahealth.lollipin.lib.managers.AppLock} by setting - * {@link com.github.omadahealth.lollipin.lib.managers.AppLockImpl} as the - * {@link com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface} - */ - public abstract void enable(); - - /** - * Disable the {@link com.github.omadahealth.lollipin.lib.managers.AppLock} by removing any - * {@link com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface} - */ - public abstract void disable(); - - /** - * Disable the {@link com.github.omadahealth.lollipin.lib.managers.AppLock} by removing any - * {@link com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface} and also delete - * all the previous saved configurations into {@link android.content.SharedPreferences} - */ - public abstract void disableAndRemoveConfiguration(); - - /** - * Get the last active time of the app used by {@link #shouldLockSceen(android.app.Activity)} - */ - public abstract long getLastActiveMillis(); - - /** - * Set the last active time of the app used by {@link #shouldLockSceen(android.app.Activity)}. - * Set in {@link com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface#onActivityPaused(android.app.Activity)} - * and {@link com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface#onActivityResumed(android.app.Activity)} - */ - public abstract void setLastActiveMillis(); - - /** - * Set the passcode (store his SHA1 into {@link android.content.SharedPreferences}) using the - * {@link com.github.omadahealth.lollipin.lib.encryption.Encryptor} class. - */ - public abstract boolean setPasscode(String passcode); - - /** - * Check the {@link android.content.SharedPreferences} to see if fingerprint authentication is - * enabled. - */ - public abstract boolean isFingerprintAuthEnabled(); - - /** - * Enable or disable fingerprint authentication on the PIN screen. - * @param enabled If true, enables the fingerprint reader if it is supported. If false, will - * hide the fingerprint reader icon on the PIN screen. - */ - public abstract void setFingerprintAuthEnabled(boolean enabled); - - /** - * Check the passcode by comparing his SHA1 into {@link android.content.SharedPreferences} using the - * {@link com.github.omadahealth.lollipin.lib.encryption.Encryptor} class. - */ - public abstract boolean checkPasscode(String passcode); - - /** - * Check the {@link android.content.SharedPreferences} to see if a password already exists - */ - public abstract boolean isPasscodeSet(); - - /** - * Check if an activity must be ignored and then don't call the - * {@link com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface} - */ - public abstract boolean isIgnoredActivity(Activity activity); - - /** - * Evaluates if: - * - we are already into the {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} - * - the passcode is not set - * - the timeout didn't reached - * If any of this is true, then we don't need to start the - * {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} (it returns false) - * Otherwise returns true - */ - public abstract boolean shouldLockSceen(Activity activity); -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java deleted file mode 100644 index c2c72574..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java +++ /dev/null @@ -1,484 +0,0 @@ -package com.github.omadahealth.lollipin.lib.managers; - -import android.content.Context; -import android.content.Intent; -import android.hardware.fingerprint.FingerprintManager; -import android.os.Build; -import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; -import android.util.Log; -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.ImageView; -import android.widget.TextView; - -import com.github.omadahealth.lollipin.lib.PinActivity; -import com.github.omadahealth.lollipin.lib.R; -import com.github.omadahealth.lollipin.lib.enums.KeyboardButtonEnum; -import com.github.omadahealth.lollipin.lib.interfaces.KeyboardButtonClickedListener; -import com.github.omadahealth.lollipin.lib.views.KeyboardView; -import com.github.omadahealth.lollipin.lib.views.PinCodeRoundView; - -import java.util.Arrays; -import java.util.List; - -/** - * Created by stoyan and olivier on 1/13/15. - * The activity that appears when the password needs to be set or has to be asked. - * Call this activity in normal or singleTop mode (not singleTask or singleInstance, it does not work - * with {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}). - */ -public abstract class AppLockActivity extends PinActivity implements KeyboardButtonClickedListener, View.OnClickListener, FingerprintUiHelper.Callback { - - public static final String TAG = AppLockActivity.class.getSimpleName(); - public static final String ACTION_CANCEL = TAG + ".actionCancelled"; - private static final int DEFAULT_PIN_LENGTH = 4; - - protected TextView mStepTextView; - protected TextView mForgotTextView; - protected PinCodeRoundView mPinCodeRoundView; - protected KeyboardView mKeyboardView; - protected ImageView mFingerprintImageView; - protected TextView mFingerprintTextView; - - protected LockManager mLockManager; - - - protected FingerprintManager mFingerprintManager; - protected FingerprintUiHelper mFingerprintUiHelper; - - protected int mType = AppLock.UNLOCK_PIN; - protected int mAttempts = 1; - protected String mPinCode; - - protected String mOldPinCode; - - private boolean isCodeSuccessful = false; - - /** - * First creation - */ - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(getContentView()); - initLayout(getIntent()); - } - - /** - * If called in singleTop mode - */ - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - - initLayout(intent); - } - - @Override - protected void onResume() { - super.onResume(); - //Init layout for Fingerprint - initLayoutForFingerprint(); - } - - @Override - protected void onPause() { - super.onPause(); - if (mFingerprintUiHelper != null) { - mFingerprintUiHelper.stopListening(); - } - } - - /** - * Init completely the layout, depending of the extra {@link com.github.omadahealth.lollipin.lib.managers.AppLock#EXTRA_TYPE} - */ - private void initLayout(Intent intent) { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) { - //Animate if greater than 2.3.3 - overridePendingTransition(R.anim.nothing, R.anim.nothing); - } - - Bundle extras = intent.getExtras(); - if (extras != null) { - mType = extras.getInt(AppLock.EXTRA_TYPE, AppLock.UNLOCK_PIN); - } - - mLockManager = LockManager.getInstance(); - mPinCode = ""; - mOldPinCode = ""; - - enableAppLockerIfDoesNotExist(); - mLockManager.getAppLock().setPinChallengeCancelled(false); - - mStepTextView = (TextView) this.findViewById(R.id.pin_code_step_textview); - mPinCodeRoundView = (PinCodeRoundView) this.findViewById(R.id.pin_code_round_view); - mPinCodeRoundView.setPinLength(this.getPinLength()); - mForgotTextView = (TextView) this.findViewById(R.id.pin_code_forgot_textview); - mForgotTextView.setOnClickListener(this); - mKeyboardView = (KeyboardView) this.findViewById(R.id.pin_code_keyboard_view); - mKeyboardView.setKeyboardButtonClickedListener(this); - - int logoId = mLockManager.getAppLock().getLogoId(); - ImageView logoImage = ((ImageView) findViewById(R.id.pin_code_logo_imageview)); - if (logoId != AppLock.LOGO_ID_NONE) { - logoImage.setVisibility(View.VISIBLE); - logoImage.setImageResource(logoId); - } - mForgotTextView.setText(getForgotText()); - setForgotTextVisibility(); - - setStepText(); - } - - /** - * Init {@link FingerprintManager} of the {@link android.os.Build.VERSION#SDK_INT} is > to Marshmallow - * and {@link FingerprintManager#isHardwareDetected()}. - */ - private void initLayoutForFingerprint() { - mFingerprintImageView = (ImageView) this.findViewById(R.id.pin_code_fingerprint_imageview); - mFingerprintTextView = (TextView) this.findViewById(R.id.pin_code_fingerprint_textview); - if (mType == AppLock.UNLOCK_PIN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - mFingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE); - mFingerprintUiHelper = new FingerprintUiHelper.FingerprintUiHelperBuilder(mFingerprintManager).build(mFingerprintImageView, mFingerprintTextView, this); - try { - if (mFingerprintManager.isHardwareDetected() && mFingerprintUiHelper.isFingerprintAuthAvailable() - && mLockManager.getAppLock().isFingerprintAuthEnabled()) { - mFingerprintImageView.setVisibility(View.VISIBLE); - mFingerprintTextView.setVisibility(View.VISIBLE); - mFingerprintUiHelper.startListening(); - } else { - mFingerprintImageView.setVisibility(View.GONE); - mFingerprintTextView.setVisibility(View.GONE); - } - } catch (SecurityException e) { - Log.e(TAG, e.toString()); - mFingerprintImageView.setVisibility(View.GONE); - mFingerprintTextView.setVisibility(View.GONE); - } - } else { - mFingerprintImageView.setVisibility(View.GONE); - mFingerprintTextView.setVisibility(View.GONE); - } - } - - /** - * Re enable {@link AppLock} if it has been collected to avoid - * {@link NullPointerException}. - */ - @SuppressWarnings("unchecked") - private void enableAppLockerIfDoesNotExist() { - try { - if (mLockManager.getAppLock() == null) { - mLockManager.enableAppLock(this, getCustomAppLockActivityClass()); - } - } catch (Exception e) { - Log.e(TAG, e.toString()); - } - } - - /** - * Init the {@link #mStepTextView} based on {@link #mType} - */ - private void setStepText() { - mStepTextView.setText(getStepText(mType)); - } - - /** - * Gets the {@link String} to be used in the {@link #mStepTextView} based on {@link #mType} - * - * @param reason The {@link #mType} to return a {@link String} for - * @return The {@link String} for the {@link AppLockActivity} - */ - public String getStepText(int reason) { - String msg = null; - switch (reason) { - case AppLock.DISABLE_PINLOCK: - msg = getString(R.string.pin_code_step_disable, this.getPinLength()); - break; - case AppLock.ENABLE_PINLOCK: - msg = getString(R.string.pin_code_step_create, this.getPinLength()); - break; - case AppLock.CHANGE_PIN: - msg = getString(R.string.pin_code_step_change, this.getPinLength()); - break; - case AppLock.UNLOCK_PIN: - msg = getString(R.string.pin_code_step_unlock, this.getPinLength()); - break; - case AppLock.CONFIRM_PIN: - msg = getString(R.string.pin_code_step_enable_confirm, this.getPinLength()); - break; - } - return msg; - } - - public String getForgotText() { - return getString(R.string.pin_code_forgot_text); - } - - private void setForgotTextVisibility(){ - mForgotTextView.setVisibility(mLockManager.getAppLock().shouldShowForgot(mType) ? View.VISIBLE : View.GONE); - } - - /** - * Overrides to allow a slide_down animation when finishing - */ - @Override - public void finish() { - super.finish(); - - //If code successful, reset the timer - if (isCodeSuccessful) { - if (mLockManager != null) { - AppLock appLock = mLockManager.getAppLock(); - if (appLock != null) { - appLock.setLastActiveMillis(); - } - } - } - - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) { - //Animate if greater than 2.3.3 - overridePendingTransition(R.anim.nothing, R.anim.slide_down); - } - } - - /** - * Add the button clicked to {@link #mPinCode} each time. - * Refreshes also the {@link com.github.omadahealth.lollipin.lib.views.PinCodeRoundView} - */ - @Override - public void onKeyboardClick(KeyboardButtonEnum keyboardButtonEnum) { - if (mPinCode.length() < this.getPinLength()) { - int value = keyboardButtonEnum.getButtonValue(); - - if (value == KeyboardButtonEnum.BUTTON_CLEAR.getButtonValue()) { - if (!mPinCode.isEmpty()) { - setPinCode(mPinCode.substring(0, mPinCode.length() - 1)); - } else { - setPinCode(""); - } - } else { - setPinCode(mPinCode + value); - } - } - } - - /** - * Called at the end of the animation of the {@link com.andexert.library.RippleView} - * Calls {@link #onPinCodeInputed} when {@link #mPinCode} - */ - @Override - public void onRippleAnimationEnd() { - if (mPinCode.length() == this.getPinLength()) { - onPinCodeInputed(); - } - } - - /** - * Switch over the {@link #mType} to determine if the password is ok, if we should pass to the next step etc... - */ - protected void onPinCodeInputed() { - switch (mType) { - case AppLock.DISABLE_PINLOCK: - if (mLockManager.getAppLock().checkPasscode(mPinCode)) { - setResult(RESULT_OK); - mLockManager.getAppLock().setPasscode(null); - onPinCodeSuccess(); - finish(); - } else { - onPinCodeError(); - } - break; - case AppLock.ENABLE_PINLOCK: - mOldPinCode = mPinCode; - setPinCode(""); - mType = AppLock.CONFIRM_PIN; - setStepText(); - setForgotTextVisibility(); - break; - case AppLock.CONFIRM_PIN: - if (mPinCode.equals(mOldPinCode)) { - setResult(RESULT_OK); - mLockManager.getAppLock().setPasscode(mPinCode); - onPinCodeSuccess(); - finish(); - } else { - mOldPinCode = ""; - setPinCode(""); - mType = AppLock.ENABLE_PINLOCK; - setStepText(); - setForgotTextVisibility(); - onPinCodeError(); - } - break; - case AppLock.CHANGE_PIN: - if (mLockManager.getAppLock().checkPasscode(mPinCode)) { - mType = AppLock.ENABLE_PINLOCK; - setStepText(); - setForgotTextVisibility(); - setPinCode(""); - onPinCodeSuccess(); - } else { - onPinCodeError(); - } - break; - case AppLock.UNLOCK_PIN: - if (mLockManager.getAppLock().checkPasscode(mPinCode)) { - setResult(RESULT_OK); - onPinCodeSuccess(); - finish(); - } else { - onPinCodeError(); - } - break; - default: - break; - } - } - - /** - * Override {@link #onBackPressed()} to prevent user for finishing the activity - */ - @Override - public void onBackPressed() { - if (getBackableTypes().contains(mType)) { - if (AppLock.UNLOCK_PIN == getType()) { - mLockManager.getAppLock().setPinChallengeCancelled(true); - LocalBroadcastManager - .getInstance(this) - .sendBroadcast(new Intent().setAction(ACTION_CANCEL)); - } - super.onBackPressed(); - } - } - - @Override - public void onAuthenticated() { - Log.e(TAG, "Fingerprint READ!!!"); - setResult(RESULT_OK); - onPinCodeSuccess(); - finish(); - } - - @Override - public void onError() { - Log.e(TAG, "Fingerprint READ ERROR!!!"); - } - - /** - * Gets the list of {@link AppLock} types that are acceptable to be backed out of using - * the device's back button - * - * @return an {@link List} of {@link AppLock} types which are backable - */ - public List getBackableTypes() { - return Arrays.asList(AppLock.CHANGE_PIN, AppLock.DISABLE_PINLOCK); - } - - /** - * Displays the information dialog when the user clicks the - * {@link #mForgotTextView} - */ - public abstract void showForgotDialog(); - - /** - * Run a shake animation when the password is not valid. - */ - protected void onPinCodeError() { - onPinFailure(mAttempts++); - Thread thread = new Thread() { - public void run() { - mPinCode = ""; - mPinCodeRoundView.refresh(mPinCode.length()); - Animation animation = AnimationUtils.loadAnimation( - AppLockActivity.this, R.anim.shake); - mKeyboardView.startAnimation(animation); - } - }; - runOnUiThread(thread); - } - - protected void onPinCodeSuccess() { - isCodeSuccessful = true; - onPinSuccess(mAttempts); - mAttempts = 1; - } - - /** - * Set the pincode and refreshes the {@link com.github.omadahealth.lollipin.lib.views.PinCodeRoundView} - */ - public void setPinCode(String pinCode) { - mPinCode = pinCode; - mPinCodeRoundView.refresh(mPinCode.length()); - } - - - /** - * Returns the type of this {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} - */ - public int getType() { - return mType; - } - - /** - * When we click on the {@link #mForgotTextView} handle the pop-up - * dialog - * - * @param view {@link #mForgotTextView} - */ - @Override - public void onClick(View view) { - showForgotDialog(); - } - - /** - * When the user has failed a pin challenge - * - * @param attempts the number of attempts the user has used - */ - public abstract void onPinFailure(int attempts); - - /** - * When the user has succeeded at a pin challenge - * - * @param attempts the number of attempts the user had used - */ - public abstract void onPinSuccess(int attempts); - - /** - * Gets the resource id to the {@link View} to be set with {@link #setContentView(int)}. - * The custom layout must include the following: - * - {@link TextView} with an id of pin_code_step_textview - * - {@link TextView} with an id of pin_code_forgot_textview - * - {@link PinCodeRoundView} with an id of pin_code_round_view - * - {@link KeyboardView} with an id of pin_code_keyboard_view - * - * @return the resource id to the {@link View} - */ - public int getContentView() { - return R.layout.activity_pin_code; - } - - /** - * Gets the number of digits in the pin code. Subclasses can override this to change the - * length of the pin. - * - * @return the number of digits in the PIN - */ - public int getPinLength() { - return AppLockActivity.DEFAULT_PIN_LENGTH; - } - - /** - * Get the current class extending {@link AppLockActivity} to re-enable {@link AppLock} - * in case it has been collected - * - * @return the current class extending {@link AppLockActivity} - */ - public Class getCustomAppLockActivityClass() { - return this.getClass(); - } -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockImpl.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockImpl.java deleted file mode 100644 index e6413c1b..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockImpl.java +++ /dev/null @@ -1,421 +0,0 @@ -package com.github.omadahealth.lollipin.lib.managers; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Build; -import android.preference.PreferenceManager; -import android.util.Base64; -import android.util.Log; - -import com.github.omadahealth.lollipin.lib.PinActivity; -import com.github.omadahealth.lollipin.lib.PinCompatActivity; -import com.github.omadahealth.lollipin.lib.PinFragmentActivity; -import com.github.omadahealth.lollipin.lib.encryption.Encryptor; -import com.github.omadahealth.lollipin.lib.enums.Algorithm; -import com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface; - -import java.security.SecureRandom; -import java.util.Arrays; - -public class AppLockImpl extends AppLock implements LifeCycleInterface { - - public static final String TAG = "AppLockImpl"; - - /** - * The {@link android.content.SharedPreferences} key used to store the password - */ - private static final String PASSWORD_PREFERENCE_KEY = "PASSCODE"; - /** - * The {@link android.content.SharedPreferences} key used to store the {@link Algorithm} - */ - private static final String PASSWORD_ALGORITHM_PREFERENCE_KEY = "ALGORITHM"; - /** - * The {@link android.content.SharedPreferences} key used to store the last active time - */ - private static final String LAST_ACTIVE_MILLIS_PREFERENCE_KEY = "LAST_ACTIVE_MILLIS"; - /** - * The {@link android.content.SharedPreferences} key used to store the timeout - */ - private static final String TIMEOUT_MILLIS_PREFERENCE_KEY = "TIMEOUT_MILLIS_PREFERENCE_KEY"; - /** - * The {@link android.content.SharedPreferences} key used to store the logo resource id - */ - private static final String LOGO_ID_PREFERENCE_KEY = "LOGO_ID_PREFERENCE_KEY"; - /** - * The {@link android.content.SharedPreferences} key used to store the forgot option - */ - private static final String SHOW_FORGOT_PREFERENCE_KEY = "SHOW_FORGOT_PREFERENCE_KEY"; - - /** - * The {@link android.content.SharedPreferences} key used to store the only background timeout option - */ - private static final String ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY = "ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY"; - /** - * The {@link SharedPreferences} key used to store whether the user has backed out of the {@link AppLockActivity} - */ - private static final String PIN_CHALLENGE_CANCELLED_PREFERENCE_KEY = "PIN_CHALLENGE_CANCELLED_PREFERENCE_KEY"; - /** - * The {@link android.content.SharedPreferences} key used to store the dynamically generated password salt - */ - private static final String PASSWORD_SALT_PREFERENCE_KEY = "PASSWORD_SALT_PREFERENCE_KEY"; - /** - * The {@link SharedPreferences} key used to store whether the caller has enabled fingerprint authentication. - * This value defaults to true for backwards compatibility. - */ - private static final String FINGERPRINT_AUTH_ENABLED_PREFERENCE_KEY = "FINGERPRINT_AUTH_ENABLED_PREFERENCE_KEY"; - /** - * The default password salt - */ - private static final String DEFAULT_PASSWORD_SALT = "7xn7@c$"; - /** - * The key algorithm used to generating the dynamic salt - */ - private static final String KEY_ALGORITHM = "PBEWithMD5AndDES"; - /** - * The key length of the salt - */ - private static final int KEY_LENGTH = 256; - /** - * The number of iterations used to generate a dynamic salt - */ - private static final int KEY_ITERATIONS = 20; - - /** - * The {@link android.content.SharedPreferences} used to store the password, the last active time etc... - */ - private SharedPreferences mSharedPreferences; - - /** - * The activity class that extends {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} - */ - private Class mActivityClass; - - /** - * Static instance of {@link AppLockImpl} - */ - private static AppLockImpl mInstance; - - /** - * Static method that allows to get back the current static Instance of {@link AppLockImpl} - * - * @param context The current context of the {@link Activity} - * @param activityClass The activity extending {@link AppLockActivity} - * @return The instance. - */ - public static AppLockImpl getInstance(Context context, Class activityClass) { - synchronized (LockManager.class) { - if (mInstance == null) { - mInstance = new AppLockImpl<>(context, activityClass); - } - } - return mInstance; - } - - private AppLockImpl(Context context, Class activityClass) { - super(); - this.mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - this.mActivityClass = activityClass; - } - - @Override - public void setTimeout(long timeout) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putLong(TIMEOUT_MILLIS_PREFERENCE_KEY, timeout); - editor.apply(); - } - - public String getSalt() { - String salt = mSharedPreferences.getString(PASSWORD_SALT_PREFERENCE_KEY, null); - if (salt == null) { - salt = generateSalt(); - setSalt(salt); - } - return salt; - } - - private void setSalt(String salt) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putString(PASSWORD_SALT_PREFERENCE_KEY, salt); - editor.apply(); - } - - private String generateSalt() { - byte[] salt = new byte[KEY_LENGTH]; - try { - SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); - sr.setSeed(System.currentTimeMillis()); - sr.nextBytes(salt); - return Arrays.toString(salt); - } catch (Exception e) { - salt = DEFAULT_PASSWORD_SALT.getBytes(); - } - return Base64.encodeToString(salt, Base64.DEFAULT); - } - - @Override - public long getTimeout() { - return mSharedPreferences.getLong(TIMEOUT_MILLIS_PREFERENCE_KEY, DEFAULT_TIMEOUT); - } - - @Override - public void setLogoId(int logoId) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putInt(LOGO_ID_PREFERENCE_KEY, logoId); - editor.apply(); - } - - @Override - public int getLogoId() { - return mSharedPreferences.getInt(LOGO_ID_PREFERENCE_KEY, LOGO_ID_NONE); - } - - @Override - public void setShouldShowForgot(boolean showForgot) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(SHOW_FORGOT_PREFERENCE_KEY, showForgot); - editor.apply(); - } - - @Override - public boolean pinChallengeCancelled() { - return mSharedPreferences.getBoolean(PIN_CHALLENGE_CANCELLED_PREFERENCE_KEY, false); - } - - @Override - public void setPinChallengeCancelled(boolean backedOut) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(PIN_CHALLENGE_CANCELLED_PREFERENCE_KEY, backedOut); - editor.apply(); - } - - @Override - public boolean shouldShowForgot(int appLockType) { - return mSharedPreferences.getBoolean(SHOW_FORGOT_PREFERENCE_KEY, true) - && appLockType != AppLock.ENABLE_PINLOCK && appLockType != AppLock.CONFIRM_PIN; - } - - @Override - public boolean onlyBackgroundTimeout() { - return mSharedPreferences.getBoolean(ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY, false); - } - - @Override - public void setOnlyBackgroundTimeout(boolean onlyBackgroundTimeout) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY, onlyBackgroundTimeout); - editor.apply(); - } - - @Override - public void enable() { - PinActivity.setListener(this); - PinCompatActivity.setListener(this); - PinFragmentActivity.setListener(this); - } - - @Override - public void disable() { - PinActivity.clearListeners(); - PinCompatActivity.clearListeners(); - PinFragmentActivity.clearListeners(); - } - - @Override - public void disableAndRemoveConfiguration() { - PinActivity.clearListeners(); - PinCompatActivity.clearListeners(); - PinFragmentActivity.clearListeners(); - mSharedPreferences.edit().remove(PASSWORD_PREFERENCE_KEY) - .remove(LAST_ACTIVE_MILLIS_PREFERENCE_KEY) - .remove(PASSWORD_ALGORITHM_PREFERENCE_KEY) - .remove(TIMEOUT_MILLIS_PREFERENCE_KEY) - .remove(LOGO_ID_PREFERENCE_KEY) - .remove(SHOW_FORGOT_PREFERENCE_KEY) - .remove(FINGERPRINT_AUTH_ENABLED_PREFERENCE_KEY) - .remove(ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY) - .apply(); - } - - @Override - public long getLastActiveMillis() { - return mSharedPreferences.getLong(LAST_ACTIVE_MILLIS_PREFERENCE_KEY, 0); - } - - @Override - public boolean isFingerprintAuthEnabled() { - return mSharedPreferences.getBoolean(FINGERPRINT_AUTH_ENABLED_PREFERENCE_KEY, true); - } - - @Override - public void setFingerprintAuthEnabled(boolean enabled) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(FINGERPRINT_AUTH_ENABLED_PREFERENCE_KEY, enabled); - editor.apply(); - } - - @Override - public void setLastActiveMillis() { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putLong(LAST_ACTIVE_MILLIS_PREFERENCE_KEY, System.currentTimeMillis()); - editor.apply(); - } - - @Override - public boolean checkPasscode(String passcode) { - Algorithm algorithm = Algorithm.getFromText(mSharedPreferences.getString(PASSWORD_ALGORITHM_PREFERENCE_KEY, "")); - - String salt = getSalt(); - passcode = salt + passcode + salt; - passcode = Encryptor.getSHA(passcode, algorithm); - String storedPasscode = ""; - - if (mSharedPreferences.contains(PASSWORD_PREFERENCE_KEY)) { - storedPasscode = mSharedPreferences.getString(PASSWORD_PREFERENCE_KEY, ""); - } - - if (storedPasscode.equalsIgnoreCase(passcode)) { - return true; - } else { - return false; - } - } - - @Override - public boolean setPasscode(String passcode) { - String salt = getSalt(); - SharedPreferences.Editor editor = mSharedPreferences.edit(); - - if (passcode == null) { - editor.remove(PASSWORD_PREFERENCE_KEY); - editor.apply(); - this.disable(); - } else { - passcode = salt + passcode + salt; - setAlgorithm(Algorithm.SHA256); - passcode = Encryptor.getSHA(passcode, Algorithm.SHA256); - editor.putString(PASSWORD_PREFERENCE_KEY, passcode); - editor.apply(); - this.enable(); - } - - return true; - } - - /** - * Set the algorithm used in {@link #setPasscode(String)} - */ - private void setAlgorithm(Algorithm algorithm) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putString(PASSWORD_ALGORITHM_PREFERENCE_KEY, algorithm.getValue()); - editor.apply(); - } - - @Override - public boolean isPasscodeSet() { - if (mSharedPreferences.contains(PASSWORD_PREFERENCE_KEY)) { - return true; - } - - return false; - } - - @Override - public boolean isIgnoredActivity(Activity activity) { - String clazzName = activity.getClass().getName(); - - // ignored activities - if (mIgnoredActivities.contains(clazzName)) { - Log.d(TAG, "ignore activity " + clazzName); - return true; - } - - return false; - } - - @Override - public boolean shouldLockSceen(Activity activity) { - Log.d(TAG, "Lollipin shouldLockSceen() called"); - - // previously backed out of pin screen - if (pinChallengeCancelled()) { - return true; - } - - // already unlock - if (activity instanceof AppLockActivity) { - AppLockActivity ala = (AppLockActivity) activity; - if (ala.getType() == AppLock.UNLOCK_PIN) { - Log.d(TAG, "already unlock activity"); - return false; - } - } - - // no pass code set - if (!isPasscodeSet()) { - Log.d(TAG, "lock passcode not set."); - return false; - } - - // no enough timeout - long lastActiveMillis = getLastActiveMillis(); - long passedTime = System.currentTimeMillis() - lastActiveMillis; - long timeout = getTimeout(); - if (lastActiveMillis > 0 && passedTime <= timeout) { - Log.d(TAG, "no enough timeout " + passedTime + " for " - + timeout); - return false; - } - - return true; - } - - @Override - public void onActivityPaused(Activity activity) { - if (isIgnoredActivity(activity)) { - return; - } - - String clazzName = activity.getClass().getName(); - Log.d(TAG, "onActivityPaused " + clazzName); - - if (!shouldLockSceen(activity) && !(activity instanceof AppLockActivity)) { - setLastActiveMillis(); - } - } - - @Override - public void onActivityUserInteraction(Activity activity) { - if (onlyBackgroundTimeout() && !shouldLockSceen(activity) && !(activity instanceof AppLockActivity)) { - setLastActiveMillis(); - } - } - - @Override - public void onActivityResumed(Activity activity) { - if (isIgnoredActivity(activity)) { - return; - } - - String clazzName = activity.getClass().getName(); - Log.d(TAG, "onActivityResumed " + clazzName); - - if (shouldLockSceen(activity)) { - Log.d(TAG, "mActivityClass.getClass() " + mActivityClass); - Intent intent = new Intent(activity.getApplicationContext(), - mActivityClass); - intent.putExtra(AppLock.EXTRA_TYPE, AppLock.UNLOCK_PIN); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - activity.getApplication().startActivity(intent); - } - - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { - return; - } - - if (!shouldLockSceen(activity) && !(activity instanceof AppLockActivity)) { - setLastActiveMillis(); - } - } -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java deleted file mode 100644 index b4febe4f..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.github.omadahealth.lollipin.lib.managers; - -import android.annotation.TargetApi; -import android.app.KeyguardManager; -import android.content.Context; -import android.hardware.fingerprint.FingerprintManager; -import android.os.Build; -import android.os.CancellationSignal; -import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyProperties; -import android.widget.ImageView; -import android.widget.TextView; - -import com.github.omadahealth.lollipin.lib.R; - -import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; - -/** - * Small helper class to manage - * - cipher keys generation and use - * - text/icon around fingerprint authentication UI. - */ -@TargetApi(Build.VERSION_CODES.M) -public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback { - - /** - * The timeout for the error to be displayed. Returns to the normal UI after this. - */ - private static final long ERROR_TIMEOUT_MILLIS = 1600; - /** - * The timeout for the success to be displayed. Calls {@link Callback#onAuthenticated()} after this. - */ - private static final long SUCCESS_DELAY_MILLIS = 1300; - /** - * Alias for our key in the Android Key Store - **/ - private static final String KEY_NAME = "my_key"; - - /** - * The {@link Cipher} used to init {@link FingerprintManager} - */ - private Cipher mCipher; - /** - * The {@link KeyStore} used to initiliaze the key {@link #KEY_NAME} - */ - private KeyStore mKeyStore; - /** - * The {@link KeyGenerator} used to generate the key {@link #KEY_NAME} - */ - private KeyGenerator mKeyGenerator; - /** - * The {@link android.hardware.fingerprint.FingerprintManager.CryptoObject} - */ - private final FingerprintManager mFingerprintManager; - /** - * The {@link ImageView} that is used to show the authent state - */ - private final ImageView mIcon; - /** - * The {@link TextView} that is used to show the authent state - */ - private final TextView mErrorTextView; - /** - * The {@link com.github.omadahealth.lollipin.lib.managers.FingerprintUiHelper.Callback} used to return success or error. - */ - private final Callback mCallback; - /** - * The {@link CancellationSignal} used after an error happens - */ - private CancellationSignal mCancellationSignal; - /** - * Used if the user cancelled the authentication by himself - */ - private boolean mSelfCancelled; - - /** - * Builder class for {@link FingerprintUiHelper} in which injected fields from Dagger - * holds its fields and takes other arguments in the {@link #build} method. - */ - public static class FingerprintUiHelperBuilder { - private final FingerprintManager mFingerPrintManager; - - public FingerprintUiHelperBuilder(FingerprintManager fingerprintManager) { - mFingerPrintManager = fingerprintManager; - } - - public FingerprintUiHelper build(ImageView icon, TextView errorTextView, Callback callback) { - return new FingerprintUiHelper(mFingerPrintManager, icon, errorTextView, - callback); - } - } - - /** - * Constructor for {@link FingerprintUiHelper}. This method is expected to be called from - * only the {@link FingerprintUiHelperBuilder} class. - */ - private FingerprintUiHelper(FingerprintManager fingerprintManager, - ImageView icon, TextView errorTextView, Callback callback) { - mFingerprintManager = fingerprintManager; - mIcon = icon; - mErrorTextView = errorTextView; - mCallback = callback; - } - - /** - * Starts listening to {@link FingerprintManager} - * - * @throws SecurityException If the hardware is not available, or the permission are not set - */ - public void startListening() throws SecurityException { - if (initCipher()) { - FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(mCipher); - if (!isFingerprintAuthAvailable()) { - return; - } - mCancellationSignal = new CancellationSignal(); - mSelfCancelled = false; - mFingerprintManager.authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null); - mIcon.setImageResource(R.drawable.ic_fp_40px); - } - } - - /** - * Stops listening to {@link FingerprintManager} - */ - public void stopListening() { - if (mCancellationSignal != null) { - mSelfCancelled = true; - mCancellationSignal.cancel(); - mCancellationSignal = null; - } - } - - /** - * Called by {@link FingerprintManager} if the authentication threw an error. - */ - @Override - public void onAuthenticationError(int errMsgId, CharSequence errString) { - if (!mSelfCancelled) { - showError(errString); - mIcon.postDelayed(new Runnable() { - @Override - public void run() { - mCallback.onError(); - } - }, ERROR_TIMEOUT_MILLIS); - } - } - - /** - * Called by {@link FingerprintManager} if the user asked for help. - */ - @Override - public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { - showError(helpString); - } - - /** - * Called by {@link FingerprintManager} if the authentication failed (bad finger etc...). - */ - @Override - public void onAuthenticationFailed() { - showError(mIcon.getResources().getString( - R.string.pin_code_fingerprint_not_recognized)); - } - - /** - * Called by {@link FingerprintManager} if the authentication succeeded. - */ - @Override - public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { - mErrorTextView.removeCallbacks(mResetErrorTextRunnable); - mIcon.setImageResource(R.drawable.ic_fingerprint_success); - mErrorTextView.setTextColor( - mErrorTextView.getResources().getColor(R.color.success_color, null)); - mErrorTextView.setText( - mErrorTextView.getResources().getString(R.string.pin_code_fingerprint_success)); - mIcon.postDelayed(new Runnable() { - @Override - public void run() { - mCallback.onAuthenticated(); - } - }, SUCCESS_DELAY_MILLIS); - } - - /** - * Tells if the {@link FingerprintManager#isHardwareDetected()}, {@link FingerprintManager#hasEnrolledFingerprints()}, - * and {@link KeyguardManager#isDeviceSecure()} - * - * @return true if yes, false otherwise - * @throws SecurityException If the hardware is not available, or the permission are not set - */ - public boolean isFingerprintAuthAvailable() throws SecurityException { - return mFingerprintManager.isHardwareDetected() - && mFingerprintManager.hasEnrolledFingerprints() - && ((KeyguardManager) mIcon.getContext().getSystemService(Context.KEYGUARD_SERVICE)).isDeviceSecure(); - } - - /** - * Initialize the {@link Cipher} instance with the created key in the {@link #createKey()} - * method. - * - * @return {@code true} if initialization is successful, {@code false} if the lock screen has - * been disabled or reset after the key was generated, or if a fingerprint got enrolled after - * the key was generated. - */ - private boolean initCipher() { - try { - if (mKeyStore == null) { - mKeyStore = KeyStore.getInstance("AndroidKeyStore"); - } - createKey(); - mKeyStore.load(null); - SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null); - mCipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); - mCipher.init(Cipher.ENCRYPT_MODE, key); - return true; - } catch (NoSuchPaddingException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException - | NoSuchAlgorithmException | InvalidKeyException e) { - return false; - } - } - - /** - * Creates a symmetric key in the Android Key Store which can only be used after the user has - * authenticated with fingerprint. - */ - public void createKey() { - // The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint - // for your flow. Use of keys is necessary if you need to know if the set of - // enrolled fingerprints has changed. - try { - // Set the alias of the entry in Android KeyStore where the key will appear - // and the constrains (purposes) in the constructor of the Builder - mKeyGenerator = KeyGenerator.getInstance( - KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); - mKeyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, - KeyProperties.PURPOSE_ENCRYPT | - KeyProperties.PURPOSE_DECRYPT) - .setBlockModes(KeyProperties.BLOCK_MODE_CBC) - // Require the user to authenticate with a fingerprint to authorize every use - // of the key - .setUserAuthenticationRequired(true) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) - .build()); - mKeyGenerator.generateKey(); - } catch (NoSuchProviderException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { - throw new RuntimeException(e); - } - } - - /** - * Show an error on the UI using {@link #mIcon} and {@link #mErrorTextView} - */ - private void showError(CharSequence error) { - mIcon.setImageResource(R.drawable.ic_fingerprint_error); - mErrorTextView.setText(error); - mErrorTextView.setTextColor( - mErrorTextView.getResources().getColor(R.color.warning_color, null)); - mErrorTextView.removeCallbacks(mResetErrorTextRunnable); - mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS); - } - - /** - * Run by {@link #showError(CharSequence)} with delay to reset the original UI after an error. - */ - Runnable mResetErrorTextRunnable = new Runnable() { - @Override - public void run() { - mErrorTextView.setTextColor( - mErrorTextView.getResources().getColor(R.color.hint_color, null)); - mErrorTextView.setText( - mErrorTextView.getResources().getString(R.string.pin_code_fingerprint_text)); - mIcon.setImageResource(R.drawable.ic_fp_40px); - } - }; - - /** - * The interface used to call the original Activity/Fragment... that uses this helper. - */ - public interface Callback { - void onAuthenticated(); - - void onError(); - } -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/LockManager.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/LockManager.java deleted file mode 100644 index 93bf83df..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/managers/LockManager.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.github.omadahealth.lollipin.lib.managers; - -import android.content.Context; - -import com.github.omadahealth.lollipin.lib.PinActivity; -import com.github.omadahealth.lollipin.lib.PinCompatActivity; -import com.github.omadahealth.lollipin.lib.PinFragmentActivity; - -/** - * Allows to handle the {@link com.github.omadahealth.lollipin.lib.managers.AppLock} from within - * the actual app calling the library. - * You must get this static instance by calling {@link #getInstance()} - */ -public class LockManager { - - /** - * The static singleton instance - */ - private static LockManager mInstance; - /** - * The static singleton instance of {@link com.github.omadahealth.lollipin.lib.managers.AppLock} - */ - private static AppLock mAppLocker; - - /** - * Used to retrieve the static instance - */ - public static LockManager getInstance() { - synchronized (LockManager.class) { - if (mInstance == null) { - mInstance = new LockManager<>(); - } - } - return mInstance; - } - - /** - * You must call that into your custom {@link android.app.Application} to enable the - * {@link com.github.omadahealth.lollipin.lib.PinActivity} - */ - public void enableAppLock(Context context, Class activityClass) { - if (mAppLocker != null) { - mAppLocker.disable(); - } - mAppLocker = AppLockImpl.getInstance(context, activityClass); - mAppLocker.enable(); - } - - /** - * Tells the app if the {@link com.github.omadahealth.lollipin.lib.managers.AppLock} is enabled or not - */ - public boolean isAppLockEnabled() { - return (mAppLocker != null && (PinActivity.hasListeners() || - PinFragmentActivity.hasListeners() || PinCompatActivity.hasListeners())); - } - - /** - * Disables the app lock by calling {@link AppLock#disable()} - */ - public void disableAppLock() { - if (mAppLocker != null) { - mAppLocker.disable(); - } - mAppLocker = null; - } - - /** - * Disables the previous app lock and set a new one - */ - public void setAppLock(AppLock appLocker) { - if (mAppLocker != null) { - mAppLocker.disable(); - } - mAppLocker = appLocker; - } - - /** - * Get the {@link AppLock}. Used for defining custom timeouts etc... - */ - public AppLock getAppLock() { - return mAppLocker; - } -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardButtonView.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardButtonView.java deleted file mode 100644 index 9d35dba8..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardButtonView.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.github.omadahealth.lollipin.lib.views; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ImageView; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.andexert.library.RippleAnimationListener; -import com.andexert.library.RippleView; -import com.github.omadahealth.lollipin.lib.R; -import com.github.omadahealth.lollipin.lib.interfaces.KeyboardButtonClickedListener; - -/** - * Created by stoyan and oliviergoutay on 1/13/15. - */ -public class KeyboardButtonView extends RelativeLayout implements RippleAnimationListener { - - private KeyboardButtonClickedListener mKeyboardButtonClickedListener; - - private Context mContext; - private RippleView mRippleView; - - public KeyboardButtonView(Context context) { - this(context, null); - } - - public KeyboardButtonView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public KeyboardButtonView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - this.mContext = context; - initializeView(attrs, defStyleAttr); - } - - private void initializeView(AttributeSet attrs, int defStyleAttr) { - if (attrs != null && !isInEditMode()) { - final TypedArray attributes = mContext.getTheme().obtainStyledAttributes(attrs, R.styleable.KeyboardButtonView, - defStyleAttr, 0); - String text = attributes.getString(R.styleable.KeyboardButtonView_lp_keyboard_button_text); - Drawable image = attributes.getDrawable(R.styleable.KeyboardButtonView_lp_keyboard_button_image); - boolean rippleEnabled = attributes.getBoolean(R.styleable.KeyboardButtonView_lp_keyboard_button_ripple_enabled, true); - - attributes.recycle(); - - LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - KeyboardButtonView view = (KeyboardButtonView) inflater.inflate(R.layout.view_keyboard_button, this); - - if (text != null) { - TextView textView = (TextView) view.findViewById(R.id.keyboard_button_textview); - if (textView != null) { - textView.setText(text); - } - } - if (image != null) { - ImageView imageView = (ImageView) view.findViewById(R.id.keyboard_button_imageview); - if (imageView != null) { - imageView.setImageDrawable(image); - imageView.setVisibility(View.VISIBLE); - } - } - - mRippleView = (RippleView) view.findViewById(R.id.pin_code_keyboard_button_ripple); - mRippleView.setRippleAnimationListener(this); - if (mRippleView != null) { - if (!rippleEnabled) { - mRippleView.setVisibility(View.INVISIBLE); - } - } - } - } - - /** - * Set by {@link com.github.omadahealth.lollipin.lib.views.KeyboardView} to returns events to - * {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} - */ - public void setOnRippleAnimationEndListener(KeyboardButtonClickedListener keyboardButtonClickedListener) { - mKeyboardButtonClickedListener = keyboardButtonClickedListener; - } - - @Override - public void onRippleAnimationEnd() { - if (mKeyboardButtonClickedListener != null) { - mKeyboardButtonClickedListener.onRippleAnimationEnd(); - } - } - - /** - * Retain touches for {@link com.andexert.library.RippleView}. - * Otherwise views above will not have the event. - */ - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - onTouchEvent(event); - return false; - } -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardView.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardView.java deleted file mode 100644 index 907b99c5..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardView.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.github.omadahealth.lollipin.lib.views; - -import android.content.Context; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.LinearLayout; - -import com.github.omadahealth.lollipin.lib.R; -import com.github.omadahealth.lollipin.lib.enums.KeyboardButtonEnum; -import com.github.omadahealth.lollipin.lib.interfaces.KeyboardButtonClickedListener; - -import java.util.ArrayList; -import java.util.List; - -/** - * Created by stoyan and olivier on 1/13/15. - */ -public class KeyboardView extends LinearLayout implements View.OnClickListener { - - private Context mContext; - private KeyboardButtonClickedListener mKeyboardButtonClickedListener; - - private List mButtons; - - public KeyboardView(Context context) { - this(context, null); - } - - public KeyboardView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - this.mContext = context; - initializeView(attrs, defStyleAttr); - } - - private void initializeView(AttributeSet attrs, int defStyleAttr) { - if (!isInEditMode()) { - LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - KeyboardView view = (KeyboardView) inflater.inflate(R.layout.view_keyboard, this); - - initKeyboardButtons(view); - } - } - - /** - * Init the keyboard buttons (onClickListener) - */ - private void initKeyboardButtons(KeyboardView view) { - mButtons = new ArrayList<>(); - mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_0)); - mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_1)); - mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_2)); - mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_3)); - mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_4)); - mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_5)); - mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_6)); - mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_7)); - mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_8)); - mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_9)); - mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_clear)); - - for(View button : mButtons) { - button.setOnClickListener(this); - } - } - - @Override - public void onClick(View v) { - if(mKeyboardButtonClickedListener == null) { - return; - } - - int id = v.getId(); - if(id == R.id.pin_code_button_0) { - mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_0); - } else if(id == R.id.pin_code_button_1) { - mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_1); - } else if(id == R.id.pin_code_button_2) { - mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_2); - } else if(id == R.id.pin_code_button_3) { - mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_3); - } else if(id == R.id.pin_code_button_4) { - mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_4); - } else if(id == R.id.pin_code_button_5) { - mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_5); - } else if(id == R.id.pin_code_button_6) { - mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_6); - } else if(id == R.id.pin_code_button_7) { - mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_7); - } else if(id == R.id.pin_code_button_8) { - mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_8); - } else if(id == R.id.pin_code_button_9) { - mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_9); - } else if(id == R.id.pin_code_button_clear) { - mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_CLEAR); - } - } - - /** - * Set the {@link com.andexert.library.RippleAnimationListener} to the - * {@link com.github.omadahealth.lollipin.lib.views.KeyboardButtonView} - */ - public void setKeyboardButtonClickedListener(KeyboardButtonClickedListener keyboardButtonClickedListener) { - this.mKeyboardButtonClickedListener = keyboardButtonClickedListener; - for(KeyboardButtonView button : mButtons) { - button.setOnRippleAnimationEndListener(mKeyboardButtonClickedListener); - } - } -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeRoundView.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeRoundView.java deleted file mode 100644 index dc1e3209..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeRoundView.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.github.omadahealth.lollipin.lib.views; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.RelativeLayout; - -import com.github.omadahealth.lollipin.lib.R; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author stoyan and oliviergoutay - * @version 1/13/15 - */ -public class PinCodeRoundView extends RelativeLayout { - - private Context mContext; - private List mRoundViews; - private int mCurrentLength; - private Drawable mEmptyDotDrawableId; - private Drawable mFullDotDrawableId; - private ViewGroup mRoundContainer; - - public PinCodeRoundView(Context context) { - this(context, null); - } - - public PinCodeRoundView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PinCodeRoundView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - this.mContext = context; - initializeView(attrs, defStyleAttr); - } - - private void initializeView(AttributeSet attrs, int defStyleAttr) { - if (attrs != null && !isInEditMode()) { - final TypedArray attributes = mContext.getTheme().obtainStyledAttributes(attrs, R.styleable.PinCodeView, - defStyleAttr, 0); - - mEmptyDotDrawableId = attributes.getDrawable(R.styleable.PinCodeView_lp_empty_pin_dot); - if (mEmptyDotDrawableId == null) { - mEmptyDotDrawableId = getResources().getDrawable(R.drawable.pin_code_round_empty); - } - mFullDotDrawableId = attributes.getDrawable(R.styleable.PinCodeView_lp_full_pin_dot); - if (mFullDotDrawableId == null) { - mFullDotDrawableId = getResources().getDrawable(R.drawable.pin_code_round_full); - } - - attributes.recycle(); - - LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - PinCodeRoundView view = (PinCodeRoundView) inflater.inflate(R.layout.view_round_pin_code, this); - mRoundContainer = (ViewGroup) view.findViewById( R.id.round_container ); - - mRoundViews = new ArrayList<>(); - } - } - - /** - * Refresh the {@link android.widget.ImageView}s to look like what typed the user - * - * @param pinLength the current pin code length typed by the user - */ - public void refresh(int pinLength) { - mCurrentLength = pinLength; - for (int i = 0; i < mRoundViews.size(); i++) { - if (pinLength - 1 >= i) { - mRoundViews.get(i).setImageDrawable(mFullDotDrawableId); - } else { - mRoundViews.get(i).setImageDrawable(mEmptyDotDrawableId); - } - } - } - - public int getCurrentLength() { - return mCurrentLength; - } - - /** - * Sets a custom empty dot drawable for the {@link ImageView}s. - * @param drawable the resource Id for a custom drawable - */ - public void setEmptyDotDrawable(Drawable drawable) { - mEmptyDotDrawableId = drawable; - } - - /** - * Sets a custom full dot drawable for the {@link ImageView}s. - * @param drawable the resource Id for a custom drawable - */ - public void setFullDotDrawable(Drawable drawable) { - mFullDotDrawableId = drawable; - } - - /** - * Sets a custom empty dot drawable for the {@link ImageView}s. - * @param drawableId the resource Id for a custom drawable - */ - public void setEmptyDotDrawable(int drawableId) { - mEmptyDotDrawableId = getResources().getDrawable(drawableId); - } - - /** - * Sets a custom full dot drawable for the {@link ImageView}s. - * @param drawableId the resource Id for a custom drawable - */ - public void setFullDotDrawable(int drawableId) { - mFullDotDrawableId = getResources().getDrawable(drawableId); - } - - /** - * Sets the length of the pin code. - * - * @param pinLength the length of the pin code - */ - public void setPinLength(int pinLength) { - LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mRoundContainer.removeAllViews(); - List temp = new ArrayList<>(pinLength); - for (int i = 0; i < pinLength; i++) { - ImageView roundView; - if (i < mRoundViews.size()) { - roundView = mRoundViews.get(i); - } else { - roundView = (ImageView) inflater.inflate(R.layout.view_round, mRoundContainer, false); - } - mRoundContainer.addView(roundView); - temp.add(roundView); - } - mRoundViews.clear(); - mRoundViews.addAll(temp); - refresh(0); - } -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeView.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeView.java deleted file mode 100644 index acaf225b..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeView.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.github.omadahealth.lollipin.lib.views; - -import android.content.Context; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; - -import com.github.omadahealth.lollipin.lib.R; - -/** - * Created by stoyan and olivier on 1/12/15. - */ -public class PinCodeView extends RelativeLayout { - - private Context mContext; - - public PinCodeView(Context context) { - this(context, null); - } - - public PinCodeView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PinCodeView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - this.mContext = context; - initializeView(attrs, defStyleAttr); - } - - private void initializeView(AttributeSet attrs, int defStyleAttr) { - LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - LinearLayout view = (LinearLayout) inflater.inflate(R.layout.activity_pin_code, this); - } - -} diff --git a/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/SquareImageView.java b/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/SquareImageView.java deleted file mode 100644 index 578a0b00..00000000 --- a/lib/src/main/java/com/github/omadahealth/lollipin/lib/views/SquareImageView.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.github.omadahealth.lollipin.lib.views; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.ImageView; - -/** - * An ImageView that shrinks its larger dimension to become square. - */ -public class SquareImageView extends android.support.v7.widget.AppCompatImageView { - public SquareImageView(Context context) { - super(context); - } - - public SquareImageView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @SuppressWarnings("SuspiciousNameCombination") - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - int measuredWidth = this.getMeasuredWidth(); - int measuredHeight = this.getMeasuredHeight(); - - if (measuredHeight > measuredWidth) { - this.setMeasuredDimension(measuredWidth, measuredWidth); - } else { - this.setMeasuredDimension(measuredHeight, measuredHeight); - } - } -} diff --git a/lib/src/main/res/anim/cycle5.xml b/lib/src/main/res/anim/cycle5.xml deleted file mode 100644 index cd36e115..00000000 --- a/lib/src/main/res/anim/cycle5.xml +++ /dev/null @@ -1,3 +0,0 @@ - - \ No newline at end of file diff --git a/lib/src/main/res/anim/nothing.xml b/lib/src/main/res/anim/nothing.xml deleted file mode 100644 index 77bb8151..00000000 --- a/lib/src/main/res/anim/nothing.xml +++ /dev/null @@ -1,5 +0,0 @@ - - \ No newline at end of file diff --git a/lib/src/main/res/anim/shake.xml b/lib/src/main/res/anim/shake.xml deleted file mode 100644 index 2c93668b..00000000 --- a/lib/src/main/res/anim/shake.xml +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file diff --git a/lib/src/main/res/anim/slide_down.xml b/lib/src/main/res/anim/slide_down.xml deleted file mode 100644 index dcbbdda2..00000000 --- a/lib/src/main/res/anim/slide_down.xml +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file diff --git a/lib/src/main/res/drawable-hdpi/ic_backspace_grey600_24dp.png b/lib/src/main/res/drawable-hdpi/ic_backspace_grey600_24dp.png deleted file mode 100755 index c6fc660f4a71a74326aadef6d565b1338a777c0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 441 zcmV;q0Y?6bP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00A*cL_t(Y$K}?&PQx$|!0}7pp=Ch42;vQpm?$4>(#(kA zKZ1A-pn{#RcPKNw2rwa5Yz9)*iO+6qWvgzBlwW+lJ3E356S7HA!2y?CCB!*98g!3M zjvw~A%xMN=vAnmLExt0L-!}A;$ylt*oa8euG*Q!9V7zCB*BJDyFgFy`9m-HrFb_e; z>S_uEVdhYVvgx*k22J=>Aj7Qr%#vC%;;plRo&sBFsG1imOpN)5jw01qQDuz53S%`b z>+Oof7_9KPwntnlzzhnF`4}cOVcLtSsXNV}l>eazW1POK^zw(&^ZTaVSL%pNBO_8P zBQoj^?rIaY^lk$~JK`=p{d#&C$4I<-Nhf5uddbkb=Inj=3x}D|n}0!GXE5LFYWMg) j7fDz48QUB7Tay`oBU3lR!e25S00000NkvXXu0mjfjFhrK diff --git a/lib/src/main/res/drawable-hdpi/ic_fp_40px.png b/lib/src/main/res/drawable-hdpi/ic_fp_40px.png deleted file mode 100644 index 48ebd8ad737272d78d0ab9141c47c84f463fa3bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7011 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 diff --git a/lib/src/main/res/drawable-hdpi/tile.9.png b/lib/src/main/res/drawable-hdpi/tile.9.png deleted file mode 100644 index 135862883e26eddce2b19db021adf62e10357ad0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^d_XM3!3HF=W8NDADajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_d9MQNTcjv*DdlK%YvZ_jLY;KT`zX$+@~lcus~rX(d9r71A} z`ThO9P5{3!^Gb##?W|{)7_*qnaG3jle#z^Ewh3!L+OJDkIO=~G=WkKx z)YHW=#NzbXDW`dx97J5>g`-%QU3m-(Q)3oRowMpf+Mg{6GF#dU6@{N2E>Avldt&i} zeWx1~PP^4<7G^_&qAJ7=S!}A3k-DQD$Z{{q}B0Qq;)}%jP{kkUl&dgkry^Pa$B|I3eN-9%2gar zrVZxzeg&>A4O)HrUu^twCyCcyl6!YlSDCCzbyw~+b$*epJ$Lzk#@q^@hi{(zuVa$` XQ4-j*u_fms&_fKKu6{1-oD!M<6|Q|= diff --git a/lib/src/main/res/drawable-mdpi/ic_fp_40px.png b/lib/src/main/res/drawable-mdpi/ic_fp_40px.png deleted file mode 100644 index 122f44257b1aa188955ab01e5bf71a5f079b1da5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4001 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 diff --git a/lib/src/main/res/drawable-xhdpi/ic_backspace_grey600_24dp.png b/lib/src/main/res/drawable-xhdpi/ic_backspace_grey600_24dp.png deleted file mode 100755 index a1265229935024dcbffe0c1b0676b630e7c6d745..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 529 zcmV+s0`C2ZP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00D_fL_t(o!|j*NPQpMC$0y<=2#ARXFNF6YgfHPO{tpBq zNKsIHBohgsBnT3 zo>RfY_(DZ5c=Uo-PnSV7}=jZU$_vo9D4?hTn7Ajc^BTRii9A-*kamR=#^ zLqXAy9$wLt@u6U5RdTxuEBr>#w65y7!U_*XDB|6raUz8k9*S`1*qd>Mh=(GKvlg7- z#Q`6+Rj3%rv#6Q?e;nKHkv!}ID||nOpX^lBP!iWJSj+MzVAYXZI(!w1^FxR%EC z#2Ig;hy;1Ov4YkXKQ@$YS2}R&tYD2ggO3z#>A)cnZVigIp(O|C+8mjLf+0Vqfiic6I!AzIy5wMn^5GhL5cr56!6-%I Tr@C(900000NkvXXu0mjf)x+7; diff --git a/lib/src/main/res/drawable-xhdpi/ic_fp_40px.png b/lib/src/main/res/drawable-xhdpi/ic_fp_40px.png deleted file mode 100644 index e1c9590bbfbcf220b08879f2f9e560a4f285f4ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10524 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 diff --git a/lib/src/main/res/drawable-xxhdpi/ic_fp_40px.png b/lib/src/main/res/drawable-xxhdpi/ic_fp_40px.png deleted file mode 100644 index f7e87240e1a3b35f0fb4b71d908ca9db7ac705bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18565 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 O0000 - - - - - diff --git a/lib/src/main/res/drawable/ic_fingerprint_success.xml b/lib/src/main/res/drawable/ic_fingerprint_success.xml deleted file mode 100644 index 261f3e7f..00000000 --- a/lib/src/main/res/drawable/ic_fingerprint_success.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - diff --git a/lib/src/main/res/drawable/pin_code_round_empty.xml b/lib/src/main/res/drawable/pin_code_round_empty.xml deleted file mode 100644 index f6951e39..00000000 --- a/lib/src/main/res/drawable/pin_code_round_empty.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/lib/src/main/res/drawable/pin_code_round_full.xml b/lib/src/main/res/drawable/pin_code_round_full.xml deleted file mode 100644 index 614b0718..00000000 --- a/lib/src/main/res/drawable/pin_code_round_full.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/lib/src/main/res/layout/activity_pin_code.xml b/lib/src/main/res/layout/activity_pin_code.xml deleted file mode 100644 index 5e087a6a..00000000 --- a/lib/src/main/res/layout/activity_pin_code.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/src/main/res/layout/view_keyboard.xml b/lib/src/main/res/layout/view_keyboard.xml deleted file mode 100644 index 79a54324..00000000 --- a/lib/src/main/res/layout/view_keyboard.xml +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/src/main/res/layout/view_keyboard_button.xml b/lib/src/main/res/layout/view_keyboard_button.xml deleted file mode 100644 index 1dd03ebf..00000000 --- a/lib/src/main/res/layout/view_keyboard_button.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/lib/src/main/res/layout/view_round.xml b/lib/src/main/res/layout/view_round.xml deleted file mode 100644 index 145056f2..00000000 --- a/lib/src/main/res/layout/view_round.xml +++ /dev/null @@ -1,11 +0,0 @@ - - diff --git a/lib/src/main/res/layout/view_round_pin_code.xml b/lib/src/main/res/layout/view_round_pin_code.xml deleted file mode 100644 index 87e84470..00000000 --- a/lib/src/main/res/layout/view_round_pin_code.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/lib/src/main/res/values-id/strings.xml b/lib/src/main/res/values-id/strings.xml deleted file mode 100644 index 9dd5b3c4..00000000 --- a/lib/src/main/res/values-id/strings.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - Lupa? - Matikan %d-digit Kode pin - Buat %d-digit Kode pin - Masukan %d-digit Kode pin Anda - Masukan %d-digit Kode pin Anda - Pastikan %d-digit Kode pin Anda - - Tanda tangan - Tanda tangan dikenali - Tanda tangan tidak dikenali. Coba lagi - - diff --git a/lib/src/main/res/values-ko/strings.xml b/lib/src/main/res/values-ko/strings.xml deleted file mode 100644 index d5195f48..00000000 --- a/lib/src/main/res/values-ko/strings.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - 잊어버렸나요? - %d-자리 핀코드 해제 - %d-자리 핀코드 생성 - 입력한 %d-자리 핀코드를 입력하세요 - %d-자리 핀코드를 입력하세요 - %d-자리 핀코드를 한번 더 입력하세요 - - 지문인식 - 지문을 인식했습니다 - 지문을 인식하지 못 했습니다. 다시 시도해 주세요 - - \ No newline at end of file diff --git a/lib/src/main/res/values-pt/strings.xml b/lib/src/main/res/values-pt/strings.xml deleted file mode 100644 index 63aec50e..00000000 --- a/lib/src/main/res/values-pt/strings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - Digital não reconhecida. Tente novamente - Digital reconhecida - Digital - Esqueceu? - Insira seu PIN atual de %d dígitos - Criar um PIN de %d dígitos - Desabilitar PIN de %d dígitos - Confirme seu PIN de %d dígitos - Insira seu PIN de %d dígitos - \ No newline at end of file diff --git a/lib/src/main/res/values-ru/strings.xml b/lib/src/main/res/values-ru/strings.xml deleted file mode 100644 index f0699a69..00000000 --- a/lib/src/main/res/values-ru/strings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - Забыли пароль? - Отключите %d-значный пин-код - Создайте %d-значный пин-код - Введите свой %d-значный пин-код - Введите свой %d-значный пин-код - Подтвердите ваш %d-значный пин-код - - Отпечаток пальца - Отпечаток принят - Отпечаток не принят. Попробуйте снова. - diff --git a/lib/src/main/res/values/attrs.xml b/lib/src/main/res/values/attrs.xml deleted file mode 100644 index 70b221a4..00000000 --- a/lib/src/main/res/values/attrs.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib/src/main/res/values/colors.xml b/lib/src/main/res/values/colors.xml deleted file mode 100644 index 8e08b31e..00000000 --- a/lib/src/main/res/values/colors.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - #4a636f - #03a9f4 - #4b636f - #f0f6f9 - #9baab2 - #eaf2f5 - - #f4511e - #42000000 - #009688 - \ No newline at end of file diff --git a/lib/src/main/res/values/dimens.xml b/lib/src/main/res/values/dimens.xml deleted file mode 100644 index 5ec65fd1..00000000 --- a/lib/src/main/res/values/dimens.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - 30sp - 15dp - 30dp - 25dp - 10dp - 20dp - 5dp - 50dp - 16sp - 20sp - 20dp - 20dp - 30dp - 10dp - - - - - \ No newline at end of file diff --git a/lib/src/main/res/values/integers.xml b/lib/src/main/res/values/integers.xml deleted file mode 100644 index ed009de7..00000000 --- a/lib/src/main/res/values/integers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - 200 - \ No newline at end of file diff --git a/lib/src/main/res/values/strings.xml b/lib/src/main/res/values/strings.xml deleted file mode 100644 index bd3e5bc0..00000000 --- a/lib/src/main/res/values/strings.xml +++ /dev/null @@ -1,34 +0,0 @@ - - lib - - 1 - 2 - ABC - 3 - DEF - 4 - GHI - 5 - JKL - 6 - MNO - 7 - PQRS - 8 - TUV - 9 - WXYZ - 0 - - Forgot? - Disable %d-digit Pincode - Create a %d-digit Pincode - Enter your current %d-digit Pincode - Enter your %d-digit Pincode - Confirm your %d-digit Pincode - - Fingerprint - Fingerprint recognized - Fingerprint not recognized. Try again - - diff --git a/lib/build.gradle b/lollipin/build.gradle similarity index 85% rename from lib/build.gradle rename to lollipin/build.gradle index 55f30cb8..b5fc3918 100644 --- a/lib/build.gradle +++ b/lollipin/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 26 - buildToolsVersion '26.0.1' + compileSdkVersion 27 + buildToolsVersion '26.0.2' defaultConfig { minSdkVersion 14 - targetSdkVersion 26 + targetSdkVersion 27 versionCode 2 versionName VERSION_NAME } @@ -26,9 +26,9 @@ dependencies { compile 'com.github.omadahealth.typefaceview:typefaceview:1.5.0@aar' //TypefaceTextView //Compat - compile 'com.android.support:support-v4:26.0.2' - compile 'com.android.support:appcompat-v7:26.0.2' - compile "com.android.support:support-v13:26.0.2" + compile 'com.android.support:support-v4:27.1.0' + compile 'com.android.support:appcompat-v7:27.1.0' + compile "com.android.support:support-v13:27.1.0" } repositories { maven { From 583eadf1eb509eef97ee489a958e1c49a03ceb68 Mon Sep 17 00:00:00 2001 From: jayba Date: Tue, 3 Apr 2018 04:46:43 +0800 Subject: [PATCH 02/27] Caused by java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.hardware.fingerprint.FingerprintManager.isHardwareDetected()' on a null object reference at com.github.omadahealth.lollipin.lib.managers.AppLockActivity.initLayoutForFingerprint(AppLockActivity.java:1147) --- lollipin/.gitignore | 1 + lollipin/proguard-rules.pro | 17 + .../lollipin/lib/ApplicationTest.java | 13 + lollipin/src/main/AndroidManifest.xml | 10 + .../omadahealth/lollipin/lib/PinActivity.java | 86 ++++ .../lollipin/lib/PinCompatActivity.java | 85 +++ .../lollipin/lib/PinFragmentActivity.java | 85 +++ .../lollipin/lib/encryption/Encryptor.java | 86 ++++ .../lollipin/lib/enums/Algorithm.java | 28 + .../lib/enums/KeyboardButtonEnum.java | 33 ++ .../KeyboardButtonClickedListener.java | 27 + .../lib/interfaces/LifeCycleInterface.java | 28 + .../lollipin/lib/managers/AppLock.java | 205 ++++++++ .../lib/managers/AppLockActivity.java | 484 ++++++++++++++++++ .../lollipin/lib/managers/AppLockImpl.java | 421 +++++++++++++++ .../lib/managers/FingerprintUiHelper.java | 315 ++++++++++++ .../lollipin/lib/managers/LockManager.java | 83 +++ .../lib/views/KeyboardButtonView.java | 105 ++++ .../lollipin/lib/views/KeyboardView.java | 115 +++++ .../lollipin/lib/views/PinCodeRoundView.java | 144 ++++++ .../lollipin/lib/views/PinCodeView.java | 39 ++ .../lollipin/lib/views/SquareImageView.java | 33 ++ lollipin/src/main/res/anim/cycle5.xml | 3 + lollipin/src/main/res/anim/nothing.xml | 5 + lollipin/src/main/res/anim/shake.xml | 6 + lollipin/src/main/res/anim/slide_down.xml | 6 + .../ic_backspace_grey600_24dp.png | Bin 0 -> 441 bytes .../src/main/res/drawable-hdpi/ic_fp_40px.png | Bin 0 -> 7011 bytes .../src/main/res/drawable-hdpi/tile.9.png | Bin 0 -> 196 bytes .../ic_backspace_grey600_24dp.png | Bin 0 -> 333 bytes .../src/main/res/drawable-mdpi/ic_fp_40px.png | Bin 0 -> 4001 bytes .../ic_backspace_grey600_24dp.png | Bin 0 -> 529 bytes .../main/res/drawable-xhdpi/ic_fp_40px.png | Bin 0 -> 10524 bytes .../ic_backspace_grey600_24dp.png | Bin 0 -> 699 bytes .../main/res/drawable-xxhdpi/ic_fp_40px.png | Bin 0 -> 18565 bytes .../ic_backspace_grey600_24dp.png | Bin 0 -> 942 bytes .../main/res/drawable-xxxhdpi/ic_fp_40px.png | Bin 0 -> 16535 bytes .../res/drawable/ic_fingerprint_error.xml | 28 + .../res/drawable/ic_fingerprint_success.xml | 28 + .../res/drawable/pin_code_round_empty.xml | 15 + .../main/res/drawable/pin_code_round_full.xml | 15 + .../src/main/res/layout/activity_pin_code.xml | 97 ++++ .../src/main/res/layout/view_keyboard.xml | 127 +++++ .../main/res/layout/view_keyboard_button.xml | 31 ++ lollipin/src/main/res/layout/view_round.xml | 11 + .../main/res/layout/view_round_pin_code.xml | 9 + lollipin/src/main/res/values-id/strings.xml | 14 + lollipin/src/main/res/values-ko/strings.xml | 14 + lollipin/src/main/res/values-pt/strings.xml | 12 + lollipin/src/main/res/values-ru/strings.xml | 12 + lollipin/src/main/res/values/attrs.xml | 18 + lollipin/src/main/res/values/colors.xml | 13 + lollipin/src/main/res/values/dimens.xml | 21 + lollipin/src/main/res/values/integers.xml | 4 + lollipin/src/main/res/values/strings.xml | 34 ++ 55 files changed, 2966 insertions(+) create mode 100644 lollipin/.gitignore create mode 100644 lollipin/proguard-rules.pro create mode 100644 lollipin/src/androidTest/java/com/github/omadahealth/lollipin/lib/ApplicationTest.java create mode 100644 lollipin/src/main/AndroidManifest.xml create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinActivity.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinCompatActivity.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinFragmentActivity.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/encryption/Encryptor.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/enums/Algorithm.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/enums/KeyboardButtonEnum.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/KeyboardButtonClickedListener.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/LifeCycleInterface.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLock.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockImpl.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/LockManager.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardButtonView.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardView.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeRoundView.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeView.java create mode 100644 lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/SquareImageView.java create mode 100644 lollipin/src/main/res/anim/cycle5.xml create mode 100644 lollipin/src/main/res/anim/nothing.xml create mode 100644 lollipin/src/main/res/anim/shake.xml create mode 100644 lollipin/src/main/res/anim/slide_down.xml create mode 100644 lollipin/src/main/res/drawable-hdpi/ic_backspace_grey600_24dp.png create mode 100644 lollipin/src/main/res/drawable-hdpi/ic_fp_40px.png create mode 100644 lollipin/src/main/res/drawable-hdpi/tile.9.png create mode 100644 lollipin/src/main/res/drawable-mdpi/ic_backspace_grey600_24dp.png create mode 100644 lollipin/src/main/res/drawable-mdpi/ic_fp_40px.png create mode 100644 lollipin/src/main/res/drawable-xhdpi/ic_backspace_grey600_24dp.png create mode 100644 lollipin/src/main/res/drawable-xhdpi/ic_fp_40px.png create mode 100644 lollipin/src/main/res/drawable-xxhdpi/ic_backspace_grey600_24dp.png create mode 100644 lollipin/src/main/res/drawable-xxhdpi/ic_fp_40px.png create mode 100644 lollipin/src/main/res/drawable-xxxhdpi/ic_backspace_grey600_24dp.png create mode 100644 lollipin/src/main/res/drawable-xxxhdpi/ic_fp_40px.png create mode 100644 lollipin/src/main/res/drawable/ic_fingerprint_error.xml create mode 100644 lollipin/src/main/res/drawable/ic_fingerprint_success.xml create mode 100644 lollipin/src/main/res/drawable/pin_code_round_empty.xml create mode 100644 lollipin/src/main/res/drawable/pin_code_round_full.xml create mode 100644 lollipin/src/main/res/layout/activity_pin_code.xml create mode 100644 lollipin/src/main/res/layout/view_keyboard.xml create mode 100644 lollipin/src/main/res/layout/view_keyboard_button.xml create mode 100644 lollipin/src/main/res/layout/view_round.xml create mode 100644 lollipin/src/main/res/layout/view_round_pin_code.xml create mode 100644 lollipin/src/main/res/values-id/strings.xml create mode 100644 lollipin/src/main/res/values-ko/strings.xml create mode 100644 lollipin/src/main/res/values-pt/strings.xml create mode 100644 lollipin/src/main/res/values-ru/strings.xml create mode 100644 lollipin/src/main/res/values/attrs.xml create mode 100644 lollipin/src/main/res/values/colors.xml create mode 100644 lollipin/src/main/res/values/dimens.xml create mode 100644 lollipin/src/main/res/values/integers.xml create mode 100644 lollipin/src/main/res/values/strings.xml diff --git a/lollipin/.gitignore b/lollipin/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/lollipin/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lollipin/proguard-rules.pro b/lollipin/proguard-rules.pro new file mode 100644 index 00000000..baf289b8 --- /dev/null +++ b/lollipin/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/stoyan/android_sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/lollipin/src/androidTest/java/com/github/omadahealth/lollipin/lib/ApplicationTest.java b/lollipin/src/androidTest/java/com/github/omadahealth/lollipin/lib/ApplicationTest.java new file mode 100644 index 00000000..9d4f292c --- /dev/null +++ b/lollipin/src/androidTest/java/com/github/omadahealth/lollipin/lib/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.github.omadahealth.lollipin.lib; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/lollipin/src/main/AndroidManifest.xml b/lollipin/src/main/AndroidManifest.xml new file mode 100644 index 00000000..836fdeef --- /dev/null +++ b/lollipin/src/main/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinActivity.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinActivity.java new file mode 100644 index 00000000..11ed7d6c --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinActivity.java @@ -0,0 +1,86 @@ +package com.github.omadahealth.lollipin.lib; + + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.v4.content.LocalBroadcastManager; + +import com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface; +import com.github.omadahealth.lollipin.lib.managers.AppLockActivity; + +/** + * Created by stoyan and olivier on 1/12/15. + * You must extend this Activity in order to support this library. + * Then to enable PinCode blocking, you must call + * {@link com.github.omadahealth.lollipin.lib.managers.LockManager#enableAppLock(android.content.Context, Class)} + */ +public class PinActivity extends Activity { + private static LifeCycleInterface mLifeCycleListener; + private final BroadcastReceiver mPinCancelledReceiver; + + public PinActivity() { + super(); + mPinCancelledReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + finish(); + } + }; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + IntentFilter filter = new IntentFilter(AppLockActivity.ACTION_CANCEL); + LocalBroadcastManager.getInstance(this).registerReceiver(mPinCancelledReceiver, filter); + } + + @Override + public void onUserInteraction() { + if (mLifeCycleListener != null){ + mLifeCycleListener.onActivityUserInteraction(PinActivity.this); + } + super.onUserInteraction(); + } + + @Override + protected void onResume() { + if (mLifeCycleListener != null) { + mLifeCycleListener.onActivityResumed(PinActivity.this); + } + super.onResume(); + } + + @Override + protected void onPause() { + if (mLifeCycleListener != null) { + mLifeCycleListener.onActivityPaused(PinActivity.this); + } + super.onPause(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + LocalBroadcastManager.getInstance(this).unregisterReceiver(mPinCancelledReceiver); + } + + public static void setListener(LifeCycleInterface listener) { + if (mLifeCycleListener != null) { + mLifeCycleListener = null; + } + mLifeCycleListener = listener; + } + + public static void clearListeners() { + mLifeCycleListener = null; + } + + public static boolean hasListeners() { + return (mLifeCycleListener != null); + } +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinCompatActivity.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinCompatActivity.java new file mode 100644 index 00000000..15c06d51 --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinCompatActivity.java @@ -0,0 +1,85 @@ +package com.github.omadahealth.lollipin.lib; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.app.AppCompatActivity; + +import com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface; +import com.github.omadahealth.lollipin.lib.managers.AppLockActivity; + +/** + * Created by callmepeanut on 16-1-14. + * You must extend this Activity in order to support this library. + * Then to enable PinCode blocking, you must call + * {@link com.github.omadahealth.lollipin.lib.managers.LockManager#enableAppLock(android.content.Context, Class)} + */ +public class PinCompatActivity extends AppCompatActivity { + private static LifeCycleInterface mLifeCycleListener; + private final BroadcastReceiver mPinCancelledReceiver; + + public PinCompatActivity() { + super(); + mPinCancelledReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + finish(); + } + }; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + IntentFilter filter = new IntentFilter(AppLockActivity.ACTION_CANCEL); + LocalBroadcastManager.getInstance(this).registerReceiver(mPinCancelledReceiver, filter); + } + + @Override + protected void onResume() { + if (mLifeCycleListener != null) { + mLifeCycleListener.onActivityResumed(PinCompatActivity.this); + } + super.onResume(); + } + + @Override + public void onUserInteraction() { + if (mLifeCycleListener != null){ + mLifeCycleListener.onActivityUserInteraction(PinCompatActivity.this); + } + super.onUserInteraction(); + } + + @Override + protected void onPause() { + if (mLifeCycleListener != null) { + mLifeCycleListener.onActivityPaused(PinCompatActivity.this); + } + super.onPause(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + LocalBroadcastManager.getInstance(this).unregisterReceiver(mPinCancelledReceiver); + } + + public static void setListener(LifeCycleInterface listener) { + if (mLifeCycleListener != null) { + mLifeCycleListener = null; + } + mLifeCycleListener = listener; + } + + public static void clearListeners() { + mLifeCycleListener = null; + } + + public static boolean hasListeners() { + return (mLifeCycleListener != null); + } +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinFragmentActivity.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinFragmentActivity.java new file mode 100644 index 00000000..78c49479 --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinFragmentActivity.java @@ -0,0 +1,85 @@ +package com.github.omadahealth.lollipin.lib; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.content.LocalBroadcastManager; + +import com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface; +import com.github.omadahealth.lollipin.lib.managers.AppLockActivity; + +/** + * Created by stoyan and olivier on 1/12/15. + * You must extend this Activity in order to support this library. + * Then to enable PinCode blocking, you must call + * {@link com.github.omadahealth.lollipin.lib.managers.LockManager#enableAppLock(android.content.Context, Class)} + */ +public class PinFragmentActivity extends FragmentActivity { + private static LifeCycleInterface mLifeCycleListener; + private final BroadcastReceiver mPinCancelledReceiver; + + public PinFragmentActivity() { + super(); + mPinCancelledReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + finish(); + } + }; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + IntentFilter filter = new IntentFilter(AppLockActivity.ACTION_CANCEL); + LocalBroadcastManager.getInstance(this).registerReceiver(mPinCancelledReceiver, filter); + } + + @Override + protected void onResume() { + if (mLifeCycleListener != null) { + mLifeCycleListener.onActivityResumed(PinFragmentActivity.this); + } + super.onResume(); + } + + @Override + public void onUserInteraction() { + if (mLifeCycleListener != null){ + mLifeCycleListener.onActivityUserInteraction(PinFragmentActivity.this); + } + super.onUserInteraction(); + } + + @Override + protected void onPause() { + if (mLifeCycleListener != null) { + mLifeCycleListener.onActivityPaused(PinFragmentActivity.this); + } + super.onPause(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + LocalBroadcastManager.getInstance(this).unregisterReceiver(mPinCancelledReceiver); + } + + public static void setListener(LifeCycleInterface listener) { + if (mLifeCycleListener != null) { + mLifeCycleListener = null; + } + mLifeCycleListener = listener; + } + + public static void clearListeners() { + mLifeCycleListener = null; + } + + public static boolean hasListeners() { + return (mLifeCycleListener != null); + } +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/encryption/Encryptor.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/encryption/Encryptor.java new file mode 100644 index 00000000..b04acf23 --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/encryption/Encryptor.java @@ -0,0 +1,86 @@ +package com.github.omadahealth.lollipin.lib.encryption; + +import android.text.TextUtils; + +import com.github.omadahealth.lollipin.lib.enums.Algorithm; + +import java.security.MessageDigest; +import java.util.Locale; + +/** + * Used by {@link com.github.omadahealth.lollipin.lib.managers.AppLockImpl} to get the SHA1 + * of the 4-digit password. + */ +public class Encryptor { + + /** + * Convert a chain of bytes into a {@link java.lang.String} + * + * @param bytes The chain of bytes + * @return The converted String + */ + private static String bytes2Hex(byte[] bytes) { + String hs = ""; + String stmp = ""; + for (int n = 0; n < bytes.length; n++) { + stmp = (Integer.toHexString(bytes[n] & 0XFF)); + if (stmp.length() == 1) { + hs += "0" + stmp; + } else { + hs += stmp; + } + } + return hs.toLowerCase(Locale.ENGLISH); + } + + /** + * Allows to get the SHA of a {@link java.lang.String} using {@link java.security.MessageDigest} + * if device does not support sha-256, fall back to sha-1 instead + */ + public static String getSHA(String text, Algorithm algorithm) { + String sha = ""; + if (TextUtils.isEmpty(text)) { + return sha; + } + + MessageDigest shaDigest = getShaDigest(algorithm); + + if (shaDigest != null) { + byte[] textBytes = text.getBytes(); + shaDigest.update(textBytes, 0, text.length()); + byte[] shahash = shaDigest.digest(); + return bytes2Hex(shahash); + } + + return null; + } + + /** + * Gets the default {@link MessageDigest} to use. + * Select {@link Algorithm#SHA256} in {@link com.github.omadahealth.lollipin.lib.managers.AppLockImpl#setPasscode(String)} + * but can be {@link Algorithm#SHA1} for older versions. + * + * @param algorithm The {@link Algorithm} to use + */ + private static MessageDigest getShaDigest(Algorithm algorithm) { + switch (algorithm) { + case SHA256: + try { + return MessageDigest.getInstance("SHA-256"); + } catch (Exception e) { + try { + return MessageDigest.getInstance("SHA-1"); + } catch (Exception e2) { + return null; + } + } + case SHA1: + default: + try { + return MessageDigest.getInstance("SHA-1"); + } catch (Exception e2) { + return null; + } + } + } +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/enums/Algorithm.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/enums/Algorithm.java new file mode 100644 index 00000000..2525e9f9 --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/enums/Algorithm.java @@ -0,0 +1,28 @@ +package com.github.omadahealth.lollipin.lib.enums; + +/** + * Created by olivier.goutay on 4/15/16. + */ +public enum Algorithm { + + SHA1("1"), SHA256("2"); + + private String mValue; + + Algorithm(String value) { + this.mValue = value; + } + + public String getValue() { + return mValue; + } + + public static Algorithm getFromText(String text) { + for (Algorithm algorithm : Algorithm.values()) { + if (algorithm.mValue.equals(text)) { + return algorithm; + } + } + return SHA1; + } +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/enums/KeyboardButtonEnum.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/enums/KeyboardButtonEnum.java new file mode 100644 index 00000000..6bb1bf0c --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/enums/KeyboardButtonEnum.java @@ -0,0 +1,33 @@ +package com.github.omadahealth.lollipin.lib.enums; + +/** + * Created by stoyan and oliviergoutay on 1/13/15. + */ +public enum KeyboardButtonEnum { + + BUTTON_0(0), + BUTTON_1(1), + BUTTON_2(2), + BUTTON_3(3), + BUTTON_4(4), + BUTTON_5(5), + BUTTON_6(6), + BUTTON_7(7), + BUTTON_8(8), + BUTTON_9(9), + BUTTON_CLEAR(-1); + + private int mButtonValue; + + KeyboardButtonEnum(int value) { + this.mButtonValue = value; + } + + /** + * Get the button value (numeric) + * @return 0-9 values for digits, -1 for clear button + */ + public int getButtonValue() { + return mButtonValue; + } +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/KeyboardButtonClickedListener.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/KeyboardButtonClickedListener.java new file mode 100644 index 00000000..73d7828a --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/KeyboardButtonClickedListener.java @@ -0,0 +1,27 @@ +package com.github.omadahealth.lollipin.lib.interfaces; + +import com.github.omadahealth.lollipin.lib.enums.KeyboardButtonEnum; + +/** + * Created by stoyan and oliviergoutay on 1/13/15. + * The {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} will implement + * this in order to receive events from {@link com.github.omadahealth.lollipin.lib.views.KeyboardButtonView} + * and {@link com.github.omadahealth.lollipin.lib.views.KeyboardView} + */ +public interface KeyboardButtonClickedListener { + + /** + * Receive the click of a button, just after a {@link android.view.View.OnClickListener} has fired. + * Called before {@link #onRippleAnimationEnd()}. + * @param keyboardButtonEnum The organized enum of the clicked button + */ + public void onKeyboardClick(KeyboardButtonEnum keyboardButtonEnum); + + /** + * Receive the end of a {@link com.andexert.library.RippleView} animation using a + * {@link com.andexert.library.RippleAnimationListener} to determine the end. + * Called after {@link #onKeyboardClick(com.github.omadahealth.lollipin.lib.enums.KeyboardButtonEnum)}. + */ + public void onRippleAnimationEnd(); + +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/LifeCycleInterface.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/LifeCycleInterface.java new file mode 100644 index 00000000..8ccbb025 --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/interfaces/LifeCycleInterface.java @@ -0,0 +1,28 @@ +package com.github.omadahealth.lollipin.lib.interfaces; + +import android.app.Activity; + +/** + * Created by stoyan on 1/12/15. + * Allows to follow the LifeCycle of the {@link com.github.omadahealth.lollipin.lib.PinActivity} + * Implemented by {@link com.github.omadahealth.lollipin.lib.managers.AppLockImpl} in order to + * determine when the app was launched for the last time and when to launch the + * {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} + */ +public interface LifeCycleInterface { + + /** + * Called in {@link android.app.Activity#onResume()} + */ + public void onActivityResumed(Activity activity); + + /** + * Called in {@link Activity#onUserInteraction()} + */ + public void onActivityUserInteraction(Activity activity); + + /** + * Called in {@link android.app.Activity#onPause()} + */ + public void onActivityPaused(Activity activity); +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLock.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLock.java new file mode 100644 index 00000000..d2fcc686 --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLock.java @@ -0,0 +1,205 @@ +package com.github.omadahealth.lollipin.lib.managers; + +import android.app.Activity; + +import java.util.HashSet; + +public abstract class AppLock { + /** + * ENABLE_PINLOCK type, uses at firt to define the password + */ + public static final int ENABLE_PINLOCK = 0; + /** + * DISABLE_PINLOCK type, uses to disable the system by asking the current password + */ + public static final int DISABLE_PINLOCK = 1; + /** + * CHANGE_PIN type, uses to change the current password + */ + public static final int CHANGE_PIN = 2; + /** + * CONFIRM_PIN type, used to confirm the new password + */ + public static final int CONFIRM_PIN = 3; + /** + * UNLOCK_PIN type, uses to ask the password to the user, in order to unlock the app + */ + public static final int UNLOCK_PIN = 4; + + /** + * LOGO_ID_NONE used to denote when a user has not set a logoId using {@link #setLogoId(int)} + */ + public static final int LOGO_ID_NONE = -1; + + /** + * EXTRA_TYPE, uses to pass to the {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} + * to determine in which type it musts be started. + */ + public static final String EXTRA_TYPE = "type"; + + /** + * DEFAULT_TIMEOUT, define the default timeout returned by {@link #getTimeout()}. + * If you want to modify it, you can call {@link #setTimeout(long)}. Will be stored using + * {@link android.content.SharedPreferences} + */ + public static final long DEFAULT_TIMEOUT = 1000 * 10; // 10sec + + /** + * A {@link java.util.HashSet} of {@link java.lang.String} which are the classes we don't want to + * take into account for the {@link com.github.omadahealth.lollipin.lib.PinActivity}. These activities + * will not log the last opened time, will not launch the + * {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} etc... + */ + protected HashSet mIgnoredActivities; + + public AppLock() { + mIgnoredActivities = new HashSet(); + } + + /** + * Add an ignored activity to the {@link java.util.HashSet} + */ + public void addIgnoredActivity(Class clazz) { + String clazzName = clazz.getName(); + this.mIgnoredActivities.add(clazzName); + } + + /** + * Remove an ignored activity to the {@link java.util.HashSet} + */ + public void removeIgnoredActivity(Class clazz) { + String clazzName = clazz.getName(); + this.mIgnoredActivities.remove(clazzName); + } + + /** + * Get the timeout used in {@link #shouldLockSceen(android.app.Activity)} + */ + public abstract long getTimeout(); + + /** + * Set the timeout used in {@link #shouldLockSceen(android.app.Activity)} + */ + public abstract void setTimeout(long timeout); + + /** + * Get logo resource id used by {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} + */ + public abstract int getLogoId(); + + /** + * Set logo resource id used by {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} + */ + public abstract void setLogoId(int logoId); + + /** + * Get the forgot option used by {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} + */ + public abstract boolean shouldShowForgot(int appLockType); + + /** + * Set the forgot option used by {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} + */ + public abstract void setShouldShowForgot(boolean showForgot); + + /** + * Get whether the user backed out of the {@link AppLockActivity} previously + */ + public abstract boolean pinChallengeCancelled(); + + /** + * Set whether the user backed out of the {@link AppLockActivity} + */ + public abstract void setPinChallengeCancelled(boolean cancelled); + + + /** + * Get the only background timeout option used to determine if the time + * spent in the activity must NOT be taken into account while calculating the timeout. + */ + public abstract boolean onlyBackgroundTimeout(); + + /** + * Set whether the time spent on the activity must NOT be taken into account when calculating timeout. + */ + public abstract void setOnlyBackgroundTimeout(boolean onlyBackgroundTimeout); + + /** + * Enable the {@link com.github.omadahealth.lollipin.lib.managers.AppLock} by setting + * {@link com.github.omadahealth.lollipin.lib.managers.AppLockImpl} as the + * {@link com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface} + */ + public abstract void enable(); + + /** + * Disable the {@link com.github.omadahealth.lollipin.lib.managers.AppLock} by removing any + * {@link com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface} + */ + public abstract void disable(); + + /** + * Disable the {@link com.github.omadahealth.lollipin.lib.managers.AppLock} by removing any + * {@link com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface} and also delete + * all the previous saved configurations into {@link android.content.SharedPreferences} + */ + public abstract void disableAndRemoveConfiguration(); + + /** + * Get the last active time of the app used by {@link #shouldLockSceen(android.app.Activity)} + */ + public abstract long getLastActiveMillis(); + + /** + * Set the last active time of the app used by {@link #shouldLockSceen(android.app.Activity)}. + * Set in {@link com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface#onActivityPaused(android.app.Activity)} + * and {@link com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface#onActivityResumed(android.app.Activity)} + */ + public abstract void setLastActiveMillis(); + + /** + * Set the passcode (store his SHA1 into {@link android.content.SharedPreferences}) using the + * {@link com.github.omadahealth.lollipin.lib.encryption.Encryptor} class. + */ + public abstract boolean setPasscode(String passcode); + + /** + * Check the {@link android.content.SharedPreferences} to see if fingerprint authentication is + * enabled. + */ + public abstract boolean isFingerprintAuthEnabled(); + + /** + * Enable or disable fingerprint authentication on the PIN screen. + * @param enabled If true, enables the fingerprint reader if it is supported. If false, will + * hide the fingerprint reader icon on the PIN screen. + */ + public abstract void setFingerprintAuthEnabled(boolean enabled); + + /** + * Check the passcode by comparing his SHA1 into {@link android.content.SharedPreferences} using the + * {@link com.github.omadahealth.lollipin.lib.encryption.Encryptor} class. + */ + public abstract boolean checkPasscode(String passcode); + + /** + * Check the {@link android.content.SharedPreferences} to see if a password already exists + */ + public abstract boolean isPasscodeSet(); + + /** + * Check if an activity must be ignored and then don't call the + * {@link com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface} + */ + public abstract boolean isIgnoredActivity(Activity activity); + + /** + * Evaluates if: + * - we are already into the {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} + * - the passcode is not set + * - the timeout didn't reached + * If any of this is true, then we don't need to start the + * {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} (it returns false) + * Otherwise returns true + */ + public abstract boolean shouldLockSceen(Activity activity); +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java new file mode 100644 index 00000000..a7b78457 --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java @@ -0,0 +1,484 @@ +package com.github.omadahealth.lollipin.lib.managers; + +import android.content.Context; +import android.content.Intent; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.ImageView; +import android.widget.TextView; + +import com.github.omadahealth.lollipin.lib.PinActivity; +import com.github.omadahealth.lollipin.lib.R; +import com.github.omadahealth.lollipin.lib.enums.KeyboardButtonEnum; +import com.github.omadahealth.lollipin.lib.interfaces.KeyboardButtonClickedListener; +import com.github.omadahealth.lollipin.lib.views.KeyboardView; +import com.github.omadahealth.lollipin.lib.views.PinCodeRoundView; + +import java.util.Arrays; +import java.util.List; + +/** + * Created by stoyan and olivier on 1/13/15. + * The activity that appears when the password needs to be set or has to be asked. + * Call this activity in normal or singleTop mode (not singleTask or singleInstance, it does not work + * with {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}). + */ +public abstract class AppLockActivity extends PinActivity implements KeyboardButtonClickedListener, View.OnClickListener, FingerprintUiHelper + .Callback { + + public static final String TAG = AppLockActivity.class.getSimpleName(); + public static final String ACTION_CANCEL = TAG + ".actionCancelled"; + private static final int DEFAULT_PIN_LENGTH = 4; + + protected TextView mStepTextView; + protected TextView mForgotTextView; + protected PinCodeRoundView mPinCodeRoundView; + protected KeyboardView mKeyboardView; + protected ImageView mFingerprintImageView; + protected TextView mFingerprintTextView; + + protected LockManager mLockManager; + + protected FingerprintManager mFingerprintManager; + protected FingerprintUiHelper mFingerprintUiHelper; + + protected int mType = AppLock.UNLOCK_PIN; + protected int mAttempts = 1; + protected String mPinCode; + + protected String mOldPinCode; + + private boolean isCodeSuccessful = false; + + /** + * First creation + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(getContentView()); + initLayout(getIntent()); + } + + /** + * If called in singleTop mode + */ + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + initLayout(intent); + } + + @Override + protected void onResume() { + super.onResume(); + //Init layout for Fingerprint + initLayoutForFingerprint(); + } + + @Override + protected void onPause() { + super.onPause(); + if (mFingerprintUiHelper != null) { + mFingerprintUiHelper.stopListening(); + } + } + + /** + * Init completely the layout, depending of the extra {@link com.github.omadahealth.lollipin.lib.managers.AppLock#EXTRA_TYPE} + */ + private void initLayout(Intent intent) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) { + //Animate if greater than 2.3.3 + overridePendingTransition(R.anim.nothing, R.anim.nothing); + } + + Bundle extras = intent.getExtras(); + if (extras != null) { + mType = extras.getInt(AppLock.EXTRA_TYPE, AppLock.UNLOCK_PIN); + } + + mLockManager = LockManager.getInstance(); + mPinCode = ""; + mOldPinCode = ""; + + enableAppLockerIfDoesNotExist(); + mLockManager.getAppLock().setPinChallengeCancelled(false); + + mStepTextView = (TextView) this.findViewById(R.id.pin_code_step_textview); + mPinCodeRoundView = (PinCodeRoundView) this.findViewById(R.id.pin_code_round_view); + mPinCodeRoundView.setPinLength(this.getPinLength()); + mForgotTextView = (TextView) this.findViewById(R.id.pin_code_forgot_textview); + mForgotTextView.setOnClickListener(this); + mKeyboardView = (KeyboardView) this.findViewById(R.id.pin_code_keyboard_view); + mKeyboardView.setKeyboardButtonClickedListener(this); + + int logoId = mLockManager.getAppLock().getLogoId(); + ImageView logoImage = ((ImageView) findViewById(R.id.pin_code_logo_imageview)); + if (logoId != AppLock.LOGO_ID_NONE) { + logoImage.setVisibility(View.VISIBLE); + logoImage.setImageResource(logoId); + } + mForgotTextView.setText(getForgotText()); + setForgotTextVisibility(); + + setStepText(); + } + + /** + * Init {@link FingerprintManager} of the {@link android.os.Build.VERSION#SDK_INT} is > to Marshmallow + * and {@link FingerprintManager#isHardwareDetected()}. + */ + private void initLayoutForFingerprint() { + mFingerprintImageView = (ImageView) this.findViewById(R.id.pin_code_fingerprint_imageview); + mFingerprintTextView = (TextView) this.findViewById(R.id.pin_code_fingerprint_textview); + if (mType == AppLock.UNLOCK_PIN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + mFingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE); + mFingerprintUiHelper = new FingerprintUiHelper.FingerprintUiHelperBuilder(mFingerprintManager).build(mFingerprintImageView, + mFingerprintTextView, this); + try { + if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected() && mFingerprintUiHelper.isFingerprintAuthAvailable() + && mLockManager.getAppLock().isFingerprintAuthEnabled()) { + mFingerprintImageView.setVisibility(View.VISIBLE); + mFingerprintTextView.setVisibility(View.VISIBLE); + mFingerprintUiHelper.startListening(); + } else { + mFingerprintImageView.setVisibility(View.GONE); + mFingerprintTextView.setVisibility(View.GONE); + } + } catch (SecurityException e) { + Log.e(TAG, e.toString()); + mFingerprintImageView.setVisibility(View.GONE); + mFingerprintTextView.setVisibility(View.GONE); + } + } else { + mFingerprintImageView.setVisibility(View.GONE); + mFingerprintTextView.setVisibility(View.GONE); + } + } + + /** + * Re enable {@link AppLock} if it has been collected to avoid + * {@link NullPointerException}. + */ + @SuppressWarnings("unchecked") + private void enableAppLockerIfDoesNotExist() { + try { + if (mLockManager.getAppLock() == null) { + mLockManager.enableAppLock(this, getCustomAppLockActivityClass()); + } + } catch (Exception e) { + Log.e(TAG, e.toString()); + } + } + + /** + * Init the {@link #mStepTextView} based on {@link #mType} + */ + private void setStepText() { + mStepTextView.setText(getStepText(mType)); + } + + /** + * Gets the {@link String} to be used in the {@link #mStepTextView} based on {@link #mType} + * + * @param reason The {@link #mType} to return a {@link String} for + * @return The {@link String} for the {@link AppLockActivity} + */ + public String getStepText(int reason) { + String msg = null; + switch (reason) { + case AppLock.DISABLE_PINLOCK: + msg = getString(R.string.pin_code_step_disable, this.getPinLength()); + break; + case AppLock.ENABLE_PINLOCK: + msg = getString(R.string.pin_code_step_create, this.getPinLength()); + break; + case AppLock.CHANGE_PIN: + msg = getString(R.string.pin_code_step_change, this.getPinLength()); + break; + case AppLock.UNLOCK_PIN: + msg = getString(R.string.pin_code_step_unlock, this.getPinLength()); + break; + case AppLock.CONFIRM_PIN: + msg = getString(R.string.pin_code_step_enable_confirm, this.getPinLength()); + break; + } + return msg; + } + + public String getForgotText() { + return getString(R.string.pin_code_forgot_text); + } + + private void setForgotTextVisibility() { + mForgotTextView.setVisibility(mLockManager.getAppLock().shouldShowForgot(mType) ? View.VISIBLE : View.GONE); + } + + /** + * Overrides to allow a slide_down animation when finishing + */ + @Override + public void finish() { + super.finish(); + + //If code successful, reset the timer + if (isCodeSuccessful) { + if (mLockManager != null) { + AppLock appLock = mLockManager.getAppLock(); + if (appLock != null) { + appLock.setLastActiveMillis(); + } + } + } + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) { + //Animate if greater than 2.3.3 + overridePendingTransition(R.anim.nothing, R.anim.slide_down); + } + } + + /** + * Add the button clicked to {@link #mPinCode} each time. + * Refreshes also the {@link com.github.omadahealth.lollipin.lib.views.PinCodeRoundView} + */ + @Override + public void onKeyboardClick(KeyboardButtonEnum keyboardButtonEnum) { + if (mPinCode.length() < this.getPinLength()) { + int value = keyboardButtonEnum.getButtonValue(); + + if (value == KeyboardButtonEnum.BUTTON_CLEAR.getButtonValue()) { + if (!mPinCode.isEmpty()) { + setPinCode(mPinCode.substring(0, mPinCode.length() - 1)); + } else { + setPinCode(""); + } + } else { + setPinCode(mPinCode + value); + } + } + } + + /** + * Called at the end of the animation of the @link com.andexert.library.RippleView + * Calls {@link #onPinCodeInputed} when {@link #mPinCode} + */ + @Override + public void onRippleAnimationEnd() { + if (mPinCode.length() == this.getPinLength()) { + onPinCodeInputed(); + } + } + + /** + * Switch over the {@link #mType} to determine if the password is ok, if we should pass to the next step etc... + */ + protected void onPinCodeInputed() { + switch (mType) { + case AppLock.DISABLE_PINLOCK: + if (mLockManager.getAppLock().checkPasscode(mPinCode)) { + setResult(RESULT_OK); + mLockManager.getAppLock().setPasscode(null); + onPinCodeSuccess(); + finish(); + } else { + onPinCodeError(); + } + break; + case AppLock.ENABLE_PINLOCK: + mOldPinCode = mPinCode; + setPinCode(""); + mType = AppLock.CONFIRM_PIN; + setStepText(); + setForgotTextVisibility(); + break; + case AppLock.CONFIRM_PIN: + if (mPinCode.equals(mOldPinCode)) { + setResult(RESULT_OK); + mLockManager.getAppLock().setPasscode(mPinCode); + onPinCodeSuccess(); + finish(); + } else { + mOldPinCode = ""; + setPinCode(""); + mType = AppLock.ENABLE_PINLOCK; + setStepText(); + setForgotTextVisibility(); + onPinCodeError(); + } + break; + case AppLock.CHANGE_PIN: + if (mLockManager.getAppLock().checkPasscode(mPinCode)) { + mType = AppLock.ENABLE_PINLOCK; + setStepText(); + setForgotTextVisibility(); + setPinCode(""); + onPinCodeSuccess(); + } else { + onPinCodeError(); + } + break; + case AppLock.UNLOCK_PIN: + if (mLockManager.getAppLock().checkPasscode(mPinCode)) { + setResult(RESULT_OK); + onPinCodeSuccess(); + finish(); + } else { + onPinCodeError(); + } + break; + default: + break; + } + } + + /** + * Override {@link #onBackPressed()} to prevent user for finishing the activity + */ + @Override + public void onBackPressed() { + if (getBackableTypes().contains(mType)) { + if (AppLock.UNLOCK_PIN == getType()) { + mLockManager.getAppLock().setPinChallengeCancelled(true); + LocalBroadcastManager + .getInstance(this) + .sendBroadcast(new Intent().setAction(ACTION_CANCEL)); + } + super.onBackPressed(); + } + } + + @Override + public void onAuthenticated() { + Log.e(TAG, "Fingerprint READ!!!"); + setResult(RESULT_OK); + onPinCodeSuccess(); + finish(); + } + + @Override + public void onError() { + Log.e(TAG, "Fingerprint READ ERROR!!!"); + } + + /** + * Gets the list of {@link AppLock} types that are acceptable to be backed out of using + * the device's back button + * + * @return an {@link List} of {@link AppLock} types which are backable + */ + public List getBackableTypes() { + return Arrays.asList(AppLock.CHANGE_PIN, AppLock.DISABLE_PINLOCK); + } + + /** + * Displays the information dialog when the user clicks the + * {@link #mForgotTextView} + */ + public abstract void showForgotDialog(); + + /** + * Run a shake animation when the password is not valid. + */ + protected void onPinCodeError() { + onPinFailure(mAttempts++); + Thread thread = new Thread() { + public void run() { + mPinCode = ""; + mPinCodeRoundView.refresh(mPinCode.length()); + Animation animation = AnimationUtils.loadAnimation( + AppLockActivity.this, R.anim.shake); + mKeyboardView.startAnimation(animation); + } + }; + runOnUiThread(thread); + } + + protected void onPinCodeSuccess() { + isCodeSuccessful = true; + onPinSuccess(mAttempts); + mAttempts = 1; + } + + /** + * Set the pincode and refreshes the {@link com.github.omadahealth.lollipin.lib.views.PinCodeRoundView} + */ + public void setPinCode(String pinCode) { + mPinCode = pinCode; + mPinCodeRoundView.refresh(mPinCode.length()); + } + + /** + * Returns the type of this {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} + */ + public int getType() { + return mType; + } + + /** + * When we click on the {@link #mForgotTextView} handle the pop-up + * dialog + * + * @param view {@link #mForgotTextView} + */ + @Override + public void onClick(View view) { + showForgotDialog(); + } + + /** + * When the user has failed a pin challenge + * + * @param attempts the number of attempts the user has used + */ + public abstract void onPinFailure(int attempts); + + /** + * When the user has succeeded at a pin challenge + * + * @param attempts the number of attempts the user had used + */ + public abstract void onPinSuccess(int attempts); + + /** + * Gets the resource id to the {@link View} to be set with {@link #setContentView(int)}. + * The custom layout must include the following: + * - {@link TextView} with an id of pin_code_step_textview + * - {@link TextView} with an id of pin_code_forgot_textview + * - {@link PinCodeRoundView} with an id of pin_code_round_view + * - {@link KeyboardView} with an id of pin_code_keyboard_view + * + * @return the resource id to the {@link View} + */ + public int getContentView() { + return R.layout.activity_pin_code; + } + + /** + * Gets the number of digits in the pin code. Subclasses can override this to change the + * length of the pin. + * + * @return the number of digits in the PIN + */ + public int getPinLength() { + return AppLockActivity.DEFAULT_PIN_LENGTH; + } + + /** + * Get the current class extending {@link AppLockActivity} to re-enable {@link AppLock} + * in case it has been collected + * + * @return the current class extending {@link AppLockActivity} + */ + public Class getCustomAppLockActivityClass() { + return this.getClass(); + } +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockImpl.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockImpl.java new file mode 100644 index 00000000..e6413c1b --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockImpl.java @@ -0,0 +1,421 @@ +package com.github.omadahealth.lollipin.lib.managers; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Build; +import android.preference.PreferenceManager; +import android.util.Base64; +import android.util.Log; + +import com.github.omadahealth.lollipin.lib.PinActivity; +import com.github.omadahealth.lollipin.lib.PinCompatActivity; +import com.github.omadahealth.lollipin.lib.PinFragmentActivity; +import com.github.omadahealth.lollipin.lib.encryption.Encryptor; +import com.github.omadahealth.lollipin.lib.enums.Algorithm; +import com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface; + +import java.security.SecureRandom; +import java.util.Arrays; + +public class AppLockImpl extends AppLock implements LifeCycleInterface { + + public static final String TAG = "AppLockImpl"; + + /** + * The {@link android.content.SharedPreferences} key used to store the password + */ + private static final String PASSWORD_PREFERENCE_KEY = "PASSCODE"; + /** + * The {@link android.content.SharedPreferences} key used to store the {@link Algorithm} + */ + private static final String PASSWORD_ALGORITHM_PREFERENCE_KEY = "ALGORITHM"; + /** + * The {@link android.content.SharedPreferences} key used to store the last active time + */ + private static final String LAST_ACTIVE_MILLIS_PREFERENCE_KEY = "LAST_ACTIVE_MILLIS"; + /** + * The {@link android.content.SharedPreferences} key used to store the timeout + */ + private static final String TIMEOUT_MILLIS_PREFERENCE_KEY = "TIMEOUT_MILLIS_PREFERENCE_KEY"; + /** + * The {@link android.content.SharedPreferences} key used to store the logo resource id + */ + private static final String LOGO_ID_PREFERENCE_KEY = "LOGO_ID_PREFERENCE_KEY"; + /** + * The {@link android.content.SharedPreferences} key used to store the forgot option + */ + private static final String SHOW_FORGOT_PREFERENCE_KEY = "SHOW_FORGOT_PREFERENCE_KEY"; + + /** + * The {@link android.content.SharedPreferences} key used to store the only background timeout option + */ + private static final String ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY = "ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY"; + /** + * The {@link SharedPreferences} key used to store whether the user has backed out of the {@link AppLockActivity} + */ + private static final String PIN_CHALLENGE_CANCELLED_PREFERENCE_KEY = "PIN_CHALLENGE_CANCELLED_PREFERENCE_KEY"; + /** + * The {@link android.content.SharedPreferences} key used to store the dynamically generated password salt + */ + private static final String PASSWORD_SALT_PREFERENCE_KEY = "PASSWORD_SALT_PREFERENCE_KEY"; + /** + * The {@link SharedPreferences} key used to store whether the caller has enabled fingerprint authentication. + * This value defaults to true for backwards compatibility. + */ + private static final String FINGERPRINT_AUTH_ENABLED_PREFERENCE_KEY = "FINGERPRINT_AUTH_ENABLED_PREFERENCE_KEY"; + /** + * The default password salt + */ + private static final String DEFAULT_PASSWORD_SALT = "7xn7@c$"; + /** + * The key algorithm used to generating the dynamic salt + */ + private static final String KEY_ALGORITHM = "PBEWithMD5AndDES"; + /** + * The key length of the salt + */ + private static final int KEY_LENGTH = 256; + /** + * The number of iterations used to generate a dynamic salt + */ + private static final int KEY_ITERATIONS = 20; + + /** + * The {@link android.content.SharedPreferences} used to store the password, the last active time etc... + */ + private SharedPreferences mSharedPreferences; + + /** + * The activity class that extends {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} + */ + private Class mActivityClass; + + /** + * Static instance of {@link AppLockImpl} + */ + private static AppLockImpl mInstance; + + /** + * Static method that allows to get back the current static Instance of {@link AppLockImpl} + * + * @param context The current context of the {@link Activity} + * @param activityClass The activity extending {@link AppLockActivity} + * @return The instance. + */ + public static AppLockImpl getInstance(Context context, Class activityClass) { + synchronized (LockManager.class) { + if (mInstance == null) { + mInstance = new AppLockImpl<>(context, activityClass); + } + } + return mInstance; + } + + private AppLockImpl(Context context, Class activityClass) { + super(); + this.mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + this.mActivityClass = activityClass; + } + + @Override + public void setTimeout(long timeout) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putLong(TIMEOUT_MILLIS_PREFERENCE_KEY, timeout); + editor.apply(); + } + + public String getSalt() { + String salt = mSharedPreferences.getString(PASSWORD_SALT_PREFERENCE_KEY, null); + if (salt == null) { + salt = generateSalt(); + setSalt(salt); + } + return salt; + } + + private void setSalt(String salt) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putString(PASSWORD_SALT_PREFERENCE_KEY, salt); + editor.apply(); + } + + private String generateSalt() { + byte[] salt = new byte[KEY_LENGTH]; + try { + SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); + sr.setSeed(System.currentTimeMillis()); + sr.nextBytes(salt); + return Arrays.toString(salt); + } catch (Exception e) { + salt = DEFAULT_PASSWORD_SALT.getBytes(); + } + return Base64.encodeToString(salt, Base64.DEFAULT); + } + + @Override + public long getTimeout() { + return mSharedPreferences.getLong(TIMEOUT_MILLIS_PREFERENCE_KEY, DEFAULT_TIMEOUT); + } + + @Override + public void setLogoId(int logoId) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putInt(LOGO_ID_PREFERENCE_KEY, logoId); + editor.apply(); + } + + @Override + public int getLogoId() { + return mSharedPreferences.getInt(LOGO_ID_PREFERENCE_KEY, LOGO_ID_NONE); + } + + @Override + public void setShouldShowForgot(boolean showForgot) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(SHOW_FORGOT_PREFERENCE_KEY, showForgot); + editor.apply(); + } + + @Override + public boolean pinChallengeCancelled() { + return mSharedPreferences.getBoolean(PIN_CHALLENGE_CANCELLED_PREFERENCE_KEY, false); + } + + @Override + public void setPinChallengeCancelled(boolean backedOut) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(PIN_CHALLENGE_CANCELLED_PREFERENCE_KEY, backedOut); + editor.apply(); + } + + @Override + public boolean shouldShowForgot(int appLockType) { + return mSharedPreferences.getBoolean(SHOW_FORGOT_PREFERENCE_KEY, true) + && appLockType != AppLock.ENABLE_PINLOCK && appLockType != AppLock.CONFIRM_PIN; + } + + @Override + public boolean onlyBackgroundTimeout() { + return mSharedPreferences.getBoolean(ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY, false); + } + + @Override + public void setOnlyBackgroundTimeout(boolean onlyBackgroundTimeout) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY, onlyBackgroundTimeout); + editor.apply(); + } + + @Override + public void enable() { + PinActivity.setListener(this); + PinCompatActivity.setListener(this); + PinFragmentActivity.setListener(this); + } + + @Override + public void disable() { + PinActivity.clearListeners(); + PinCompatActivity.clearListeners(); + PinFragmentActivity.clearListeners(); + } + + @Override + public void disableAndRemoveConfiguration() { + PinActivity.clearListeners(); + PinCompatActivity.clearListeners(); + PinFragmentActivity.clearListeners(); + mSharedPreferences.edit().remove(PASSWORD_PREFERENCE_KEY) + .remove(LAST_ACTIVE_MILLIS_PREFERENCE_KEY) + .remove(PASSWORD_ALGORITHM_PREFERENCE_KEY) + .remove(TIMEOUT_MILLIS_PREFERENCE_KEY) + .remove(LOGO_ID_PREFERENCE_KEY) + .remove(SHOW_FORGOT_PREFERENCE_KEY) + .remove(FINGERPRINT_AUTH_ENABLED_PREFERENCE_KEY) + .remove(ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY) + .apply(); + } + + @Override + public long getLastActiveMillis() { + return mSharedPreferences.getLong(LAST_ACTIVE_MILLIS_PREFERENCE_KEY, 0); + } + + @Override + public boolean isFingerprintAuthEnabled() { + return mSharedPreferences.getBoolean(FINGERPRINT_AUTH_ENABLED_PREFERENCE_KEY, true); + } + + @Override + public void setFingerprintAuthEnabled(boolean enabled) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(FINGERPRINT_AUTH_ENABLED_PREFERENCE_KEY, enabled); + editor.apply(); + } + + @Override + public void setLastActiveMillis() { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putLong(LAST_ACTIVE_MILLIS_PREFERENCE_KEY, System.currentTimeMillis()); + editor.apply(); + } + + @Override + public boolean checkPasscode(String passcode) { + Algorithm algorithm = Algorithm.getFromText(mSharedPreferences.getString(PASSWORD_ALGORITHM_PREFERENCE_KEY, "")); + + String salt = getSalt(); + passcode = salt + passcode + salt; + passcode = Encryptor.getSHA(passcode, algorithm); + String storedPasscode = ""; + + if (mSharedPreferences.contains(PASSWORD_PREFERENCE_KEY)) { + storedPasscode = mSharedPreferences.getString(PASSWORD_PREFERENCE_KEY, ""); + } + + if (storedPasscode.equalsIgnoreCase(passcode)) { + return true; + } else { + return false; + } + } + + @Override + public boolean setPasscode(String passcode) { + String salt = getSalt(); + SharedPreferences.Editor editor = mSharedPreferences.edit(); + + if (passcode == null) { + editor.remove(PASSWORD_PREFERENCE_KEY); + editor.apply(); + this.disable(); + } else { + passcode = salt + passcode + salt; + setAlgorithm(Algorithm.SHA256); + passcode = Encryptor.getSHA(passcode, Algorithm.SHA256); + editor.putString(PASSWORD_PREFERENCE_KEY, passcode); + editor.apply(); + this.enable(); + } + + return true; + } + + /** + * Set the algorithm used in {@link #setPasscode(String)} + */ + private void setAlgorithm(Algorithm algorithm) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putString(PASSWORD_ALGORITHM_PREFERENCE_KEY, algorithm.getValue()); + editor.apply(); + } + + @Override + public boolean isPasscodeSet() { + if (mSharedPreferences.contains(PASSWORD_PREFERENCE_KEY)) { + return true; + } + + return false; + } + + @Override + public boolean isIgnoredActivity(Activity activity) { + String clazzName = activity.getClass().getName(); + + // ignored activities + if (mIgnoredActivities.contains(clazzName)) { + Log.d(TAG, "ignore activity " + clazzName); + return true; + } + + return false; + } + + @Override + public boolean shouldLockSceen(Activity activity) { + Log.d(TAG, "Lollipin shouldLockSceen() called"); + + // previously backed out of pin screen + if (pinChallengeCancelled()) { + return true; + } + + // already unlock + if (activity instanceof AppLockActivity) { + AppLockActivity ala = (AppLockActivity) activity; + if (ala.getType() == AppLock.UNLOCK_PIN) { + Log.d(TAG, "already unlock activity"); + return false; + } + } + + // no pass code set + if (!isPasscodeSet()) { + Log.d(TAG, "lock passcode not set."); + return false; + } + + // no enough timeout + long lastActiveMillis = getLastActiveMillis(); + long passedTime = System.currentTimeMillis() - lastActiveMillis; + long timeout = getTimeout(); + if (lastActiveMillis > 0 && passedTime <= timeout) { + Log.d(TAG, "no enough timeout " + passedTime + " for " + + timeout); + return false; + } + + return true; + } + + @Override + public void onActivityPaused(Activity activity) { + if (isIgnoredActivity(activity)) { + return; + } + + String clazzName = activity.getClass().getName(); + Log.d(TAG, "onActivityPaused " + clazzName); + + if (!shouldLockSceen(activity) && !(activity instanceof AppLockActivity)) { + setLastActiveMillis(); + } + } + + @Override + public void onActivityUserInteraction(Activity activity) { + if (onlyBackgroundTimeout() && !shouldLockSceen(activity) && !(activity instanceof AppLockActivity)) { + setLastActiveMillis(); + } + } + + @Override + public void onActivityResumed(Activity activity) { + if (isIgnoredActivity(activity)) { + return; + } + + String clazzName = activity.getClass().getName(); + Log.d(TAG, "onActivityResumed " + clazzName); + + if (shouldLockSceen(activity)) { + Log.d(TAG, "mActivityClass.getClass() " + mActivityClass); + Intent intent = new Intent(activity.getApplicationContext(), + mActivityClass); + intent.putExtra(AppLock.EXTRA_TYPE, AppLock.UNLOCK_PIN); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + activity.getApplication().startActivity(intent); + } + + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { + return; + } + + if (!shouldLockSceen(activity) && !(activity instanceof AppLockActivity)) { + setLastActiveMillis(); + } + } +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java new file mode 100644 index 00000000..5dc3e8e0 --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.github.omadahealth.lollipin.lib.managers; + +import android.annotation.TargetApi; +import android.app.KeyguardManager; +import android.content.Context; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Build; +import android.os.CancellationSignal; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; +import android.widget.ImageView; +import android.widget.TextView; + +import com.github.omadahealth.lollipin.lib.R; + +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; + +/** + * Small helper class to manage + * - cipher keys generation and use + * - text/icon around fingerprint authentication UI. + */ +@TargetApi(Build.VERSION_CODES.M) +public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback { + + /** + * The timeout for the error to be displayed. Returns to the normal UI after this. + */ + private static final long ERROR_TIMEOUT_MILLIS = 1600; + /** + * The timeout for the success to be displayed. Calls {@link Callback#onAuthenticated()} after this. + */ + private static final long SUCCESS_DELAY_MILLIS = 1300; + /** + * Alias for our key in the Android Key Store + **/ + private static final String KEY_NAME = "my_key"; + + /** + * The {@link Cipher} used to init {@link FingerprintManager} + */ + private Cipher mCipher; + /** + * The {@link KeyStore} used to initiliaze the key {@link #KEY_NAME} + */ + private KeyStore mKeyStore; + /** + * The {@link KeyGenerator} used to generate the key {@link #KEY_NAME} + */ + private KeyGenerator mKeyGenerator; + /** + * The {@link android.hardware.fingerprint.FingerprintManager.CryptoObject} + */ + private final FingerprintManager mFingerprintManager; + /** + * The {@link ImageView} that is used to show the authent state + */ + private final ImageView mIcon; + /** + * The {@link TextView} that is used to show the authent state + */ + private final TextView mErrorTextView; + /** + * The {@link com.github.omadahealth.lollipin.lib.managers.FingerprintUiHelper.Callback} used to return success or error. + */ + private final Callback mCallback; + /** + * The {@link CancellationSignal} used after an error happens + */ + private CancellationSignal mCancellationSignal; + /** + * Used if the user cancelled the authentication by himself + */ + private boolean mSelfCancelled; + + /** + * Builder class for {@link FingerprintUiHelper} in which injected fields from Dagger + * holds its fields and takes other arguments in the {@link #build} method. + */ + public static class FingerprintUiHelperBuilder { + private final FingerprintManager mFingerPrintManager; + + public FingerprintUiHelperBuilder(FingerprintManager fingerprintManager) { + mFingerPrintManager = fingerprintManager; + } + + public FingerprintUiHelper build(ImageView icon, TextView errorTextView, Callback callback) { + return new FingerprintUiHelper(mFingerPrintManager, icon, errorTextView, + callback); + } + } + + /** + * Constructor for {@link FingerprintUiHelper}. This method is expected to be called from + * only the {@link FingerprintUiHelperBuilder} class. + */ + private FingerprintUiHelper(FingerprintManager fingerprintManager, + ImageView icon, TextView errorTextView, Callback callback) { + mFingerprintManager = fingerprintManager; + mIcon = icon; + mErrorTextView = errorTextView; + mCallback = callback; + } + + /** + * Starts listening to {@link FingerprintManager} + * + * @throws SecurityException If the hardware is not available, or the permission are not set + */ + public void startListening() throws SecurityException { + if (initCipher()) { + FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(mCipher); + if (!isFingerprintAuthAvailable()) { + return; + } + mCancellationSignal = new CancellationSignal(); + mSelfCancelled = false; + mFingerprintManager.authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null); + mIcon.setImageResource(R.drawable.ic_fp_40px); + } + } + + /** + * Stops listening to {@link FingerprintManager} + */ + public void stopListening() { + if (mCancellationSignal != null) { + mSelfCancelled = true; + mCancellationSignal.cancel(); + mCancellationSignal = null; + } + } + + /** + * Called by {@link FingerprintManager} if the authentication threw an error. + */ + @Override + public void onAuthenticationError(int errMsgId, CharSequence errString) { + if (!mSelfCancelled) { + showError(errString); + mIcon.postDelayed(new Runnable() { + @Override + public void run() { + mCallback.onError(); + } + }, ERROR_TIMEOUT_MILLIS); + } + } + + /** + * Called by {@link FingerprintManager} if the user asked for help. + */ + @Override + public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { + showError(helpString); + } + + /** + * Called by {@link FingerprintManager} if the authentication failed (bad finger etc...). + */ + @Override + public void onAuthenticationFailed() { + showError(mIcon.getResources().getString( + R.string.pin_code_fingerprint_not_recognized)); + } + + /** + * Called by {@link FingerprintManager} if the authentication succeeded. + */ + @Override + public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { + mErrorTextView.removeCallbacks(mResetErrorTextRunnable); + mIcon.setImageResource(R.drawable.ic_fingerprint_success); + mErrorTextView.setTextColor( + mErrorTextView.getResources().getColor(R.color.success_color, null)); + mErrorTextView.setText( + mErrorTextView.getResources().getString(R.string.pin_code_fingerprint_success)); + mIcon.postDelayed(new Runnable() { + @Override + public void run() { + mCallback.onAuthenticated(); + } + }, SUCCESS_DELAY_MILLIS); + } + + /** + * Tells if the {@link FingerprintManager#isHardwareDetected()}, {@link FingerprintManager#hasEnrolledFingerprints()}, + * and {@link KeyguardManager#isDeviceSecure()} + * + * @return true if yes, false otherwise + * @throws SecurityException If the hardware is not available, or the permission are not set + */ + public boolean isFingerprintAuthAvailable() throws SecurityException { + return mFingerprintManager != null && mFingerprintManager.isHardwareDetected() + && mFingerprintManager.hasEnrolledFingerprints() + && ((KeyguardManager) mIcon.getContext().getSystemService(Context.KEYGUARD_SERVICE)).isDeviceSecure(); + } + + /** + * Initialize the {@link Cipher} instance with the created key in the {@link #createKey()} + * method. + * + * @return {@code true} if initialization is successful, {@code false} if the lock screen has + * been disabled or reset after the key was generated, or if a fingerprint got enrolled after + * the key was generated. + */ + private boolean initCipher() { + try { + if (mKeyStore == null) { + mKeyStore = KeyStore.getInstance("AndroidKeyStore"); + } + createKey(); + mKeyStore.load(null); + SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null); + mCipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); + mCipher.init(Cipher.ENCRYPT_MODE, key); + return true; + } catch (NoSuchPaddingException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException + | NoSuchAlgorithmException | InvalidKeyException e) { + return false; + } + } + + /** + * Creates a symmetric key in the Android Key Store which can only be used after the user has + * authenticated with fingerprint. + */ + public void createKey() { + // The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint + // for your flow. Use of keys is necessary if you need to know if the set of + // enrolled fingerprints has changed. + try { + // Set the alias of the entry in Android KeyStore where the key will appear + // and the constrains (purposes) in the constructor of the Builder + mKeyGenerator = KeyGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); + mKeyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, + KeyProperties.PURPOSE_ENCRYPT | + KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + // Require the user to authenticate with a fingerprint to authorize every use + // of the key + .setUserAuthenticationRequired(true) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + .build()); + mKeyGenerator.generateKey(); + } catch (NoSuchProviderException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { + throw new RuntimeException(e); + } + } + + /** + * Show an error on the UI using {@link #mIcon} and {@link #mErrorTextView} + */ + private void showError(CharSequence error) { + mIcon.setImageResource(R.drawable.ic_fingerprint_error); + mErrorTextView.setText(error); + mErrorTextView.setTextColor( + mErrorTextView.getResources().getColor(R.color.warning_color, null)); + mErrorTextView.removeCallbacks(mResetErrorTextRunnable); + mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS); + } + + /** + * Run by {@link #showError(CharSequence)} with delay to reset the original UI after an error. + */ + Runnable mResetErrorTextRunnable = new Runnable() { + @Override + public void run() { + mErrorTextView.setTextColor( + mErrorTextView.getResources().getColor(R.color.hint_color, null)); + mErrorTextView.setText( + mErrorTextView.getResources().getString(R.string.pin_code_fingerprint_text)); + mIcon.setImageResource(R.drawable.ic_fp_40px); + } + }; + + /** + * The interface used to call the original Activity/Fragment... that uses this helper. + */ + public interface Callback { + void onAuthenticated(); + + void onError(); + } +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/LockManager.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/LockManager.java new file mode 100644 index 00000000..93bf83df --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/LockManager.java @@ -0,0 +1,83 @@ +package com.github.omadahealth.lollipin.lib.managers; + +import android.content.Context; + +import com.github.omadahealth.lollipin.lib.PinActivity; +import com.github.omadahealth.lollipin.lib.PinCompatActivity; +import com.github.omadahealth.lollipin.lib.PinFragmentActivity; + +/** + * Allows to handle the {@link com.github.omadahealth.lollipin.lib.managers.AppLock} from within + * the actual app calling the library. + * You must get this static instance by calling {@link #getInstance()} + */ +public class LockManager { + + /** + * The static singleton instance + */ + private static LockManager mInstance; + /** + * The static singleton instance of {@link com.github.omadahealth.lollipin.lib.managers.AppLock} + */ + private static AppLock mAppLocker; + + /** + * Used to retrieve the static instance + */ + public static LockManager getInstance() { + synchronized (LockManager.class) { + if (mInstance == null) { + mInstance = new LockManager<>(); + } + } + return mInstance; + } + + /** + * You must call that into your custom {@link android.app.Application} to enable the + * {@link com.github.omadahealth.lollipin.lib.PinActivity} + */ + public void enableAppLock(Context context, Class activityClass) { + if (mAppLocker != null) { + mAppLocker.disable(); + } + mAppLocker = AppLockImpl.getInstance(context, activityClass); + mAppLocker.enable(); + } + + /** + * Tells the app if the {@link com.github.omadahealth.lollipin.lib.managers.AppLock} is enabled or not + */ + public boolean isAppLockEnabled() { + return (mAppLocker != null && (PinActivity.hasListeners() || + PinFragmentActivity.hasListeners() || PinCompatActivity.hasListeners())); + } + + /** + * Disables the app lock by calling {@link AppLock#disable()} + */ + public void disableAppLock() { + if (mAppLocker != null) { + mAppLocker.disable(); + } + mAppLocker = null; + } + + /** + * Disables the previous app lock and set a new one + */ + public void setAppLock(AppLock appLocker) { + if (mAppLocker != null) { + mAppLocker.disable(); + } + mAppLocker = appLocker; + } + + /** + * Get the {@link AppLock}. Used for defining custom timeouts etc... + */ + public AppLock getAppLock() { + return mAppLocker; + } +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardButtonView.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardButtonView.java new file mode 100644 index 00000000..9d35dba8 --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardButtonView.java @@ -0,0 +1,105 @@ +package com.github.omadahealth.lollipin.lib.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.andexert.library.RippleAnimationListener; +import com.andexert.library.RippleView; +import com.github.omadahealth.lollipin.lib.R; +import com.github.omadahealth.lollipin.lib.interfaces.KeyboardButtonClickedListener; + +/** + * Created by stoyan and oliviergoutay on 1/13/15. + */ +public class KeyboardButtonView extends RelativeLayout implements RippleAnimationListener { + + private KeyboardButtonClickedListener mKeyboardButtonClickedListener; + + private Context mContext; + private RippleView mRippleView; + + public KeyboardButtonView(Context context) { + this(context, null); + } + + public KeyboardButtonView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyboardButtonView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + this.mContext = context; + initializeView(attrs, defStyleAttr); + } + + private void initializeView(AttributeSet attrs, int defStyleAttr) { + if (attrs != null && !isInEditMode()) { + final TypedArray attributes = mContext.getTheme().obtainStyledAttributes(attrs, R.styleable.KeyboardButtonView, + defStyleAttr, 0); + String text = attributes.getString(R.styleable.KeyboardButtonView_lp_keyboard_button_text); + Drawable image = attributes.getDrawable(R.styleable.KeyboardButtonView_lp_keyboard_button_image); + boolean rippleEnabled = attributes.getBoolean(R.styleable.KeyboardButtonView_lp_keyboard_button_ripple_enabled, true); + + attributes.recycle(); + + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + KeyboardButtonView view = (KeyboardButtonView) inflater.inflate(R.layout.view_keyboard_button, this); + + if (text != null) { + TextView textView = (TextView) view.findViewById(R.id.keyboard_button_textview); + if (textView != null) { + textView.setText(text); + } + } + if (image != null) { + ImageView imageView = (ImageView) view.findViewById(R.id.keyboard_button_imageview); + if (imageView != null) { + imageView.setImageDrawable(image); + imageView.setVisibility(View.VISIBLE); + } + } + + mRippleView = (RippleView) view.findViewById(R.id.pin_code_keyboard_button_ripple); + mRippleView.setRippleAnimationListener(this); + if (mRippleView != null) { + if (!rippleEnabled) { + mRippleView.setVisibility(View.INVISIBLE); + } + } + } + } + + /** + * Set by {@link com.github.omadahealth.lollipin.lib.views.KeyboardView} to returns events to + * {@link com.github.omadahealth.lollipin.lib.managers.AppLockActivity} + */ + public void setOnRippleAnimationEndListener(KeyboardButtonClickedListener keyboardButtonClickedListener) { + mKeyboardButtonClickedListener = keyboardButtonClickedListener; + } + + @Override + public void onRippleAnimationEnd() { + if (mKeyboardButtonClickedListener != null) { + mKeyboardButtonClickedListener.onRippleAnimationEnd(); + } + } + + /** + * Retain touches for {@link com.andexert.library.RippleView}. + * Otherwise views above will not have the event. + */ + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + onTouchEvent(event); + return false; + } +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardView.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardView.java new file mode 100644 index 00000000..907b99c5 --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/KeyboardView.java @@ -0,0 +1,115 @@ +package com.github.omadahealth.lollipin.lib.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; + +import com.github.omadahealth.lollipin.lib.R; +import com.github.omadahealth.lollipin.lib.enums.KeyboardButtonEnum; +import com.github.omadahealth.lollipin.lib.interfaces.KeyboardButtonClickedListener; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by stoyan and olivier on 1/13/15. + */ +public class KeyboardView extends LinearLayout implements View.OnClickListener { + + private Context mContext; + private KeyboardButtonClickedListener mKeyboardButtonClickedListener; + + private List mButtons; + + public KeyboardView(Context context) { + this(context, null); + } + + public KeyboardView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + this.mContext = context; + initializeView(attrs, defStyleAttr); + } + + private void initializeView(AttributeSet attrs, int defStyleAttr) { + if (!isInEditMode()) { + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + KeyboardView view = (KeyboardView) inflater.inflate(R.layout.view_keyboard, this); + + initKeyboardButtons(view); + } + } + + /** + * Init the keyboard buttons (onClickListener) + */ + private void initKeyboardButtons(KeyboardView view) { + mButtons = new ArrayList<>(); + mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_0)); + mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_1)); + mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_2)); + mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_3)); + mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_4)); + mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_5)); + mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_6)); + mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_7)); + mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_8)); + mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_9)); + mButtons.add((KeyboardButtonView) view.findViewById(R.id.pin_code_button_clear)); + + for(View button : mButtons) { + button.setOnClickListener(this); + } + } + + @Override + public void onClick(View v) { + if(mKeyboardButtonClickedListener == null) { + return; + } + + int id = v.getId(); + if(id == R.id.pin_code_button_0) { + mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_0); + } else if(id == R.id.pin_code_button_1) { + mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_1); + } else if(id == R.id.pin_code_button_2) { + mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_2); + } else if(id == R.id.pin_code_button_3) { + mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_3); + } else if(id == R.id.pin_code_button_4) { + mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_4); + } else if(id == R.id.pin_code_button_5) { + mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_5); + } else if(id == R.id.pin_code_button_6) { + mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_6); + } else if(id == R.id.pin_code_button_7) { + mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_7); + } else if(id == R.id.pin_code_button_8) { + mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_8); + } else if(id == R.id.pin_code_button_9) { + mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_9); + } else if(id == R.id.pin_code_button_clear) { + mKeyboardButtonClickedListener.onKeyboardClick(KeyboardButtonEnum.BUTTON_CLEAR); + } + } + + /** + * Set the {@link com.andexert.library.RippleAnimationListener} to the + * {@link com.github.omadahealth.lollipin.lib.views.KeyboardButtonView} + */ + public void setKeyboardButtonClickedListener(KeyboardButtonClickedListener keyboardButtonClickedListener) { + this.mKeyboardButtonClickedListener = keyboardButtonClickedListener; + for(KeyboardButtonView button : mButtons) { + button.setOnRippleAnimationEndListener(mKeyboardButtonClickedListener); + } + } +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeRoundView.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeRoundView.java new file mode 100644 index 00000000..dc1e3209 --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeRoundView.java @@ -0,0 +1,144 @@ +package com.github.omadahealth.lollipin.lib.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.RelativeLayout; + +import com.github.omadahealth.lollipin.lib.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author stoyan and oliviergoutay + * @version 1/13/15 + */ +public class PinCodeRoundView extends RelativeLayout { + + private Context mContext; + private List mRoundViews; + private int mCurrentLength; + private Drawable mEmptyDotDrawableId; + private Drawable mFullDotDrawableId; + private ViewGroup mRoundContainer; + + public PinCodeRoundView(Context context) { + this(context, null); + } + + public PinCodeRoundView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PinCodeRoundView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + this.mContext = context; + initializeView(attrs, defStyleAttr); + } + + private void initializeView(AttributeSet attrs, int defStyleAttr) { + if (attrs != null && !isInEditMode()) { + final TypedArray attributes = mContext.getTheme().obtainStyledAttributes(attrs, R.styleable.PinCodeView, + defStyleAttr, 0); + + mEmptyDotDrawableId = attributes.getDrawable(R.styleable.PinCodeView_lp_empty_pin_dot); + if (mEmptyDotDrawableId == null) { + mEmptyDotDrawableId = getResources().getDrawable(R.drawable.pin_code_round_empty); + } + mFullDotDrawableId = attributes.getDrawable(R.styleable.PinCodeView_lp_full_pin_dot); + if (mFullDotDrawableId == null) { + mFullDotDrawableId = getResources().getDrawable(R.drawable.pin_code_round_full); + } + + attributes.recycle(); + + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + PinCodeRoundView view = (PinCodeRoundView) inflater.inflate(R.layout.view_round_pin_code, this); + mRoundContainer = (ViewGroup) view.findViewById( R.id.round_container ); + + mRoundViews = new ArrayList<>(); + } + } + + /** + * Refresh the {@link android.widget.ImageView}s to look like what typed the user + * + * @param pinLength the current pin code length typed by the user + */ + public void refresh(int pinLength) { + mCurrentLength = pinLength; + for (int i = 0; i < mRoundViews.size(); i++) { + if (pinLength - 1 >= i) { + mRoundViews.get(i).setImageDrawable(mFullDotDrawableId); + } else { + mRoundViews.get(i).setImageDrawable(mEmptyDotDrawableId); + } + } + } + + public int getCurrentLength() { + return mCurrentLength; + } + + /** + * Sets a custom empty dot drawable for the {@link ImageView}s. + * @param drawable the resource Id for a custom drawable + */ + public void setEmptyDotDrawable(Drawable drawable) { + mEmptyDotDrawableId = drawable; + } + + /** + * Sets a custom full dot drawable for the {@link ImageView}s. + * @param drawable the resource Id for a custom drawable + */ + public void setFullDotDrawable(Drawable drawable) { + mFullDotDrawableId = drawable; + } + + /** + * Sets a custom empty dot drawable for the {@link ImageView}s. + * @param drawableId the resource Id for a custom drawable + */ + public void setEmptyDotDrawable(int drawableId) { + mEmptyDotDrawableId = getResources().getDrawable(drawableId); + } + + /** + * Sets a custom full dot drawable for the {@link ImageView}s. + * @param drawableId the resource Id for a custom drawable + */ + public void setFullDotDrawable(int drawableId) { + mFullDotDrawableId = getResources().getDrawable(drawableId); + } + + /** + * Sets the length of the pin code. + * + * @param pinLength the length of the pin code + */ + public void setPinLength(int pinLength) { + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mRoundContainer.removeAllViews(); + List temp = new ArrayList<>(pinLength); + for (int i = 0; i < pinLength; i++) { + ImageView roundView; + if (i < mRoundViews.size()) { + roundView = mRoundViews.get(i); + } else { + roundView = (ImageView) inflater.inflate(R.layout.view_round, mRoundContainer, false); + } + mRoundContainer.addView(roundView); + temp.add(roundView); + } + mRoundViews.clear(); + mRoundViews.addAll(temp); + refresh(0); + } +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeView.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeView.java new file mode 100644 index 00000000..acaf225b --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/PinCodeView.java @@ -0,0 +1,39 @@ +package com.github.omadahealth.lollipin.lib.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + +import com.github.omadahealth.lollipin.lib.R; + +/** + * Created by stoyan and olivier on 1/12/15. + */ +public class PinCodeView extends RelativeLayout { + + private Context mContext; + + public PinCodeView(Context context) { + this(context, null); + } + + public PinCodeView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PinCodeView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + this.mContext = context; + initializeView(attrs, defStyleAttr); + } + + private void initializeView(AttributeSet attrs, int defStyleAttr) { + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LinearLayout view = (LinearLayout) inflater.inflate(R.layout.activity_pin_code, this); + } + +} diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/SquareImageView.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/SquareImageView.java new file mode 100644 index 00000000..578a0b00 --- /dev/null +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/SquareImageView.java @@ -0,0 +1,33 @@ +package com.github.omadahealth.lollipin.lib.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageView; + +/** + * An ImageView that shrinks its larger dimension to become square. + */ +public class SquareImageView extends android.support.v7.widget.AppCompatImageView { + public SquareImageView(Context context) { + super(context); + } + + public SquareImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @SuppressWarnings("SuspiciousNameCombination") + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int measuredWidth = this.getMeasuredWidth(); + int measuredHeight = this.getMeasuredHeight(); + + if (measuredHeight > measuredWidth) { + this.setMeasuredDimension(measuredWidth, measuredWidth); + } else { + this.setMeasuredDimension(measuredHeight, measuredHeight); + } + } +} diff --git a/lollipin/src/main/res/anim/cycle5.xml b/lollipin/src/main/res/anim/cycle5.xml new file mode 100644 index 00000000..cd36e115 --- /dev/null +++ b/lollipin/src/main/res/anim/cycle5.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/lollipin/src/main/res/anim/nothing.xml b/lollipin/src/main/res/anim/nothing.xml new file mode 100644 index 00000000..77bb8151 --- /dev/null +++ b/lollipin/src/main/res/anim/nothing.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/lollipin/src/main/res/anim/shake.xml b/lollipin/src/main/res/anim/shake.xml new file mode 100644 index 00000000..2c93668b --- /dev/null +++ b/lollipin/src/main/res/anim/shake.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/lollipin/src/main/res/anim/slide_down.xml b/lollipin/src/main/res/anim/slide_down.xml new file mode 100644 index 00000000..dcbbdda2 --- /dev/null +++ b/lollipin/src/main/res/anim/slide_down.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/lollipin/src/main/res/drawable-hdpi/ic_backspace_grey600_24dp.png b/lollipin/src/main/res/drawable-hdpi/ic_backspace_grey600_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..c6fc660f4a71a74326aadef6d565b1338a777c0c GIT binary patch literal 441 zcmV;q0Y?6bP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00A*cL_t(Y$K}?&PQx$|!0}7pp=Ch42;vQpm?$4>(#(kA zKZ1A-pn{#RcPKNw2rwa5Yz9)*iO+6qWvgzBlwW+lJ3E356S7HA!2y?CCB!*98g!3M zjvw~A%xMN=vAnmLExt0L-!}A;$ylt*oa8euG*Q!9V7zCB*BJDyFgFy`9m-HrFb_e; z>S_uEVdhYVvgx*k22J=>Aj7Qr%#vC%;;plRo&sBFsG1imOpN)5jw01qQDuz53S%`b z>+Oof7_9KPwntnlzzhnF`4}cOVcLtSsXNV}l>eazW1POK^zw(&^ZTaVSL%pNBO_8P zBQoj^?rIaY^lk$~JK`=p{d#&C$4I<-Nhf5uddbkb=Inj=3x}D|n}0!GXE5LFYWMg) j7fDz48QUB7Tay`oBU3lR!e25S00000NkvXXu0mjfjFhrK literal 0 HcmV?d00001 diff --git a/lollipin/src/main/res/drawable-hdpi/ic_fp_40px.png b/lollipin/src/main/res/drawable-hdpi/ic_fp_40px.png new file mode 100644 index 0000000000000000000000000000000000000000..48ebd8ad737272d78d0ab9141c47c84f463fa3bf GIT binary patch literal 7011 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 literal 0 HcmV?d00001 diff --git a/lollipin/src/main/res/drawable-hdpi/tile.9.png b/lollipin/src/main/res/drawable-hdpi/tile.9.png new file mode 100644 index 0000000000000000000000000000000000000000..135862883e26eddce2b19db021adf62e10357ad0 GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^d_XM3!3HF=W8NDADajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_d9MQNTcjv*DdlK%YvZ_jLY;KT`zX$+@~lcus~rX(d9r71A} z`ThO9P5{3!^Gb##?W|{)7_*qnaG3jle#z^Ewh3!L+OJDkIO=~G=WkKx z)YHW=#NzbXDW`dx97J5>g`-%QU3m-(Q)3oRowMpf+Mg{6GF#dU6@{N2E>Avldt&i} zeWx1~PP^4<7G^_&qAJ7=S!}A3k-DQD$Z{{q}B0Qq;)}%jP{kkUl&dgkry^Pa$B|I3eN-9%2gar zrVZxzeg&>A4O)HrUu^twCyCcyl6!YlSDCCzbyw~+b$*epJ$Lzk#@q^@hi{(zuVa$` XQ4-j*u_fms&_fKKu6{1-oD!M<6|Q|= literal 0 HcmV?d00001 diff --git a/lollipin/src/main/res/drawable-mdpi/ic_fp_40px.png b/lollipin/src/main/res/drawable-mdpi/ic_fp_40px.png new file mode 100644 index 0000000000000000000000000000000000000000..122f44257b1aa188955ab01e5bf71a5f079b1da5 GIT binary patch literal 4001 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 literal 0 HcmV?d00001 diff --git a/lollipin/src/main/res/drawable-xhdpi/ic_backspace_grey600_24dp.png b/lollipin/src/main/res/drawable-xhdpi/ic_backspace_grey600_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..a1265229935024dcbffe0c1b0676b630e7c6d745 GIT binary patch literal 529 zcmV+s0`C2ZP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00D_fL_t(o!|j*NPQpMC$0y<=2#ARXFNF6YgfHPO{tpBq zNKsIHBohgsBnT3 zo>RfY_(DZ5c=Uo-PnSV7}=jZU$_vo9D4?hTn7Ajc^BTRii9A-*kamR=#^ zLqXAy9$wLt@u6U5RdTxuEBr>#w65y7!U_*XDB|6raUz8k9*S`1*qd>Mh=(GKvlg7- z#Q`6+Rj3%rv#6Q?e;nKHkv!}ID||nOpX^lBP!iWJSj+MzVAYXZI(!w1^FxR%EC z#2Ig;hy;1Ov4YkXKQ@$YS2}R&tYD2ggO3z#>A)cnZVigIp(O|C+8mjLf+0Vqfiic6I!AzIy5wMn^5GhL5cr56!6-%I Tr@C(900000NkvXXu0mjf)x+7; literal 0 HcmV?d00001 diff --git a/lollipin/src/main/res/drawable-xhdpi/ic_fp_40px.png b/lollipin/src/main/res/drawable-xhdpi/ic_fp_40px.png new file mode 100644 index 0000000000000000000000000000000000000000..e1c9590bbfbcf220b08879f2f9e560a4f285f4ec GIT binary patch literal 10524 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 literal 0 HcmV?d00001 diff --git a/lollipin/src/main/res/drawable-xxhdpi/ic_fp_40px.png b/lollipin/src/main/res/drawable-xxhdpi/ic_fp_40px.png new file mode 100644 index 0000000000000000000000000000000000000000..f7e87240e1a3b35f0fb4b71d908ca9db7ac705bf GIT binary patch literal 18565 zcmW(+1yCE^77p%K+}(n^yEbUC;_gn-0>!Op(c>PJ3om+KR`5B zQji6_|M$xORhA6hgW{^F=MDg{D*d;>l!&EO000WwA7!PqeAZ5Xd1qT|f0KB7%XUBe zrIust?bUjZI-ljCRnM|AocA%7o(ptyeDA;u8v1}+^Z|F62)FPD6E-gi3L+kXo+_IBqBnW-;8$>+T#V1ROv^IB!W2b{Y#>n&1dyP6>)+%0`_0)ZnRWH zjwaRI?ndqd-&p*QR0JSO@2-?6Bdf0bWlQYYWeSaYg7FEx6uOaWGo4SU{f|I|migsLv3b(4%{iPpH=Fu)Oh#H-j&it}SW^kFg#0BPtzLt>n2DW$Z8cvZJ zM-hR!Bnjj|gB>++h-#Ns`b`qU7ND=-7in2rF!)YHYaYwfPb_%I86IR86>CL;1P~o0 z_A9Pxx)m6fp1b>opyrG0@ARNC&hhQU>o)v+D@H`(%QK5uOC4FC$y?-e!KYj!1l8>I z9u87-E_9LAm1>XU%%syEBj6*21ahZRyh81vj6Fh0$|8t`h7%wwi>`%$0cc)g?0(yy z>J+BNbN-BqI;-zc6BUI+J53yFMWi^y>V!PU0pH8vtdAAHKhnxePqLA89vs@P1>)Ap z*%Fa>8V{N>K118O#kz+9T&d!OxFa%Gv^C*obJ-zpq3jf?$p71&bA0Q#^xs{OvB=Pq zf#?C(TCc3AT9z`S1R;}DG^(=Iv1)AAvWx-Vj4q{DcEBCnU>qD9)@}mcW=i-*({*20 zR+-Quh?qsk%R9-`0sqj{!yw56vQ2d;SJHKth}k*?*GT$tCm@L9<@FcGy+?01o?)qe zjg;HMekpTEIyTdjan>OTus}JVkwx&r1mqqY&$;c zSnE9n#H z)}0b*#<|i8hAq)Dkbx5=X1#gcF{(WeEVS&Zp$AJ*l`IBX%Z32Taf5w61PvOMB0YKl zFz+j!^KB(cJS71r(_d+D@&<$2_ylOCP0{@5QwnH_t*&sgPk+hCJv#Gb(#{N;6tq zMQgfkQtQju)qHoCA8QZS5)ELb;CWy3{39`JE`K+C>SsGOkB|~ymKqkRwjHo8l=JS+ z5ZZec`V0f;U2nw^Dx;eJ-mWhm*n?T@AG-5Wq1^+z=2#$ZaR4FH*NKo^=gkyQMRyMFdMgJF6dl0r@aiK|>+8`bDI)JzESNJee2c2Mr~ z0ri%w@gBe)7GfTWi7=mbYKlQc25_5;5Y^9`Y%EC#hxlD^6CCn1PKxy~!=9?)8FEU0(`>Fiqs5Om8*T@DpC+mFhE-Q7XqONrCmGw21pb zituJDZo}WEkNwGQGqE9M>uL^?4s3jVBuOats*HKDdV5&yoOc8<{7BF_#u*+w^SbM& zN}7BnXo32692fuq{Ae^!;aIUmX1}I?_KYWdvF6R{0-CUDm$$yOGZrppXk5M{Qy$xG zVdJ>ThZzC#=7{ZdNr7Ywkc-*;A((A#u|Hl zX5!39S3Mo}`D#KVKUAFZg)Uhso-lfwOT}&3`|X~RW%-&IkfgDvwB7q&JvAIVV)A|s zXDX|%zu?M`wj7zex%P0XTySye*ig(I`$wz4#uHvya9LTUsG>*0AoHe!H&r#DW?UV_ zVh<`Qub_UNa_sSmJbYf9+dv-a&;jkDQ3i0RdS>&7>y>^F{B|iv#HBgo?C_#fs{7zm ztBXp6gA?^B=QgnO^JA||c2EYVyzHtp_2~YWJVy{?4~_uqri5s0gyqXkoi|Wq8#)X6 z1Vj4~5V&`iISh;QL_LnOR;70nu1ho|sS1wEJ?-anzSHi#>!9YI`L{66>Q6*yCAAfk z_O$q1CaF2Ccf_#{ru3XUn1^xXgye`?&>ur@8wy#Bjna=&RL9Gu4RkVXB#TA>^=y1l>|Vq5^M%%p#(4PB_j zeNJEq`XlPOy(6aR_SbI(`!7}vTA52Mx8vwW*Vpf_4mdb}qUFX@PkLehW?9IKn#yML z4CK9@N=zM*F(MqYNVHRCyBCL$+cUyFkcB z*)?J&;5m{}^tf67{J+YTvKXZ&%}b{ld@ATdGI2;by3S9l#b;efS2Z={n0|53oT2DD z{=+OIHR7%WF*twx`QK3w!(>aPN(XhAV>A~XH+ z*jtwL7#~0*e~qr)4)>=j+D3-BPsS}b4fX_vc#6+vX1;TE6hbzy4hG8#{DZ98~&s-u8#AVyj>3tU{re>0^eH zDuJ_jkeKaPoSuu_`@Q7AMw5G1L+0CVpg9zSCQjOc5qfAIlYuub8#UO;3jFMtlkv~CtZe-Ml zp!Jx_TCX0R))G+ZZ+cr&nyIz^58vJdAeK>i%dATFufo`YO=WPWChqTfD(amA8S~#6 z63r*15VZ^@&cF77m%k{bLi>mtMm$1+X{c?nhiJX5Yqi#8#M452bxFBbibm{Gvm_v9 z!5*v$Y+X?s{FG8QD>T@g%BA}#Mm40t=DJ8(j#b!N_ z{DjgnHhq4!{W#$*uk#yYgDq}Q5g$uu4}aLEp+|RbGdY%QF`p`1zZvMGFUd|sFh<|) zH*6dd&5zx01HW|}OxcAqvwnb}Bt-+%UW5G51f`uz%tj{;A zd=|w@@iGYO4^<(jq5_v5Vw)pAE3et>Nu-a8-_nbiM=c}S* zKyBB4&ixDWtf|+|aqhvv+1p$L;Wa{6=m1F;&z0^J{Kj`YWm%?G_JiqveW{0a1N2pC z)EQBO_pj>x=IFuagV&<_*9c_7O9|g1<4tW@Lkmyy)xZ2;!P`ZGA+ne)X89KtAupUE zNkbq5bS|Jy9m+maoy-wK6U!lw8a9v8BOp3d*AIokjtHhgt~Wbo7kwPCJ`weSmv;S8 z`f;(UB27*c;aOPR`2A*Ka;$lwgjX_dY^X20wiOK%D;~WV!lePKs#~&M^u%L@SJ+V^ zkJ0mGHF@|3zU{(GvCMSaS>yp)gBmoMb!6e!FYCSj!gjyk3gkl{8x`_SCq>xKg%}oC zL4H<3Fl0)^+u|^IkZFzm3q2{6U$|n*qsQV9*#xEi$xv9u*R1FH6mAZHrOt^OYd+t|79{N}l%{E*X5;45rA;0We^nwhyS<`M-xeVqA}ZF10-LXR zA0hX+orf}YPzWC4&+>-zdkkx;(;uTW1$+ro%j78MBheh2(>rxiQfOMew4^B&n*slP ze+UKGGo|xDNA*_=8g{hsy)q}T6MZ3bOYnj>SMJ&@sHpB;Fk0yl&UAF&-N$}V-f^WB zHxe;|sy)aoJ-?olC^$)7LmEh_0fYyIlelfb@PL=FO!Ab3#- zN3i)}nO`29>UMch*S7D7dU$$KB@=^7okV`jSV;{44p~#0aPH@3^SN)+$m#h={P9*E%7imXB}91B{-jxrj$=v^9fvO|Bg3d3QH=e`1FoL=hkZECmW?UB z*7@!H6Vz{s?m#dpKhNFpZJYW;#Fs2OsNE`v5COS3j9L)AA16UCU80MW+^NRa z!C6)~F+}S_jf$NsJ14I&V!se5KBl0-4LuH81RG#t53isHQNjZN3%sVtFeQ9sV3-9- zZv!434!tNx@r1Z8ESgyoX=I{O6n0ts`tdpvP_62tY;M%TkN!Vmc!0EMFxKRmW7oLX z3k(86Npt6Mf;hGY=ONj!#&?c?XEzBqSlwIKA&;H%?g3x>Y66*RV5X64wS2q|VSGwH zh;E0PWnjnl-^1ZKkw$PryGe9MlW>FooG7aSCsxiW8)sO#ZzGVOQ=0?(n-*~LFipdZ zH@UwSF0$dXv^gMVD)Tw`*;yZIUEa@Sp!jEg z31sy_U1J7Uhl;kRESz9s#MlT#Feqo3i<+Q(D#UygzHCWvnJ8t^{W}G)7`scWYCUV&- zeO1O=>k)TEFwrMqc^*>6Q)^$f1iEZ232|{gN+9cwxoyjbJx72WOCq#@H;sXNbv-fd zR#$ZaFHKiU+wUXckdD2k*5eOZlD}+{l0|tkjB+j-n<$0R(K!oew{Br2zqapnYq#Ge zp>ejAkxC;oG4CS4AX5vN$M#I*0-bcT09fXN zmWUlP=`bM33&z7hKFZ!Q*(l@R2HIl(({txZ)NWq=)K7EDTpj}XI@1a7&zoCJtp zE@}$^YqYtoE0#t^l-~bJP<*{?C9V4ttB5LrT>otb2LXM+t9xB^`!H5fwMSjMORTF; z6~Xz|U<5fH($9@<=(Yvwz8jC3TUbS<2{}h{6~me>2rW`)DhxD5RzN~ZoFL>9<&RHP zCa}mL15o|s81N$VXlauMhL?JvPQ}I9I;a-4RN&;CIvt&!$-8UH-~}O9^tN^3{%Y!U zy`?AIk)SY%1m}IFkSQXjOTOgXD*jYpZi}()4bw;kUk9hE_V=_LqkP##1m7qP#}0fg zH`nSS_H>-r{m^y5@A(IzN{aINpEswVXgq$*p?)jmge5I|wG7rg{7+7zHMxD8eF&wz z9Rm2W-qILr%VP>(x5JjASpy#&p7Np9$->nVp2K-HSmqUuh=d%x`zGbngVm3aj@$x? zUjHCTHL;A=bps!aB_EbM?KNrdBIWWKY1*Ix{o2t zqRrz#N4~`KI%T5=O{r4+eWzo=OKDDHTr#)Jk0(APK``lt`M)|Y(+)~+Xp101d!Kvm;<~v~)e+WT zY1iF2MlyT{)Cdi=`q?_zp$xtIKJEB2lZ73Twhs%|Ri0^;-*~MPP9pFG<>f`7cQ7&7 zOwbt6k6_*)h|a!)Bo2y?^KSqw-^vUtIuvU%4=Ll;o(B`DGp&viIzC7}a1XP>U5<+B zeGx^s`Ql>-b|w@2f(N{q+ene;#!;Y>k#nBw7J?RkoI6UPi2lS+M2VB^NIU>ZW{g6` zEQQ~WzzImE(J??=+qH-`H6p?+YIk+h>D|-cCl-z42gr`xTl5_y3%u{(xXR34kR_p# zuNNaUVw-vhZQG1}6x(z)A{-)8bRZM*LhlXmh0EP+##k+k^ASblLH+%;2TQ`0x@4$& znt$^6ue`S(k+`Fieb^Ezd19%UAUEnXQDZm_VuS)0ckb}qn-tmEd88Ie8Z~h9jF;Do z;%Klo^jo{@v-5_>?-Jb>M-=6SFi58^rwzaO^Dfp-*S%O6#<~|3MT`O!t}-7%}%F zxhLGnC-Z)P$Lf8>fOe!Xi0@8zznf!syA@KvX2u6ffaLHs@qo(N3gR}H@&yYt=#0P=5N7UbJO2CYjr+>C3*;27C@?Ej zYlX8V-nnXFxx6!QmUU6XD%Qdd8NB|s@$FpgV)B4I3M`#5S)R*=HVBJb2pe!K^NaFe zUrUs&Zw)(ykM2Y7^3ukQ%v=2ZH*9dEg2a*1*9KTv;qWhM6_OJ4SpuSH-BglFnk1AR zrx-_$4iOL^P8B$r!NG@ng@Dj^-+y13u$5&brHXW$=T_H33{3ps-ga_a`YTIfq!W@b zi<+SPB^PbkZKs1>ULMTJo!fsIEQOAaQDE7{w2!tE3ye=}+(7UDpv&umS=;T(X5G?e z=IVhSN^4P|G>>hXZT3C~5!_f#=-4E;b+$*{tc(ao(_sT|$tbw)KVX}4@$h2U(QfeI|(5~ z@(e_7d7&+_>H)xRrpUnsb2u-@GnB(mDk3;BS*Ec&&*CZb?FaK{m#tZ~QVIqvAykss zipc%>#RH=&MDk#s{}AULg>ZBc92}DnI}yr*6re=GC$`BB%pTzNG!|VE1m>k*x?T-)E&F_Rg z*9n5+ys#6z4k^sAeUk9V3*`^B$Jvy(J~^%gWyBf|Qx<8r`;`dS#xoq9XP&?0)?bTL z8iPxF#`R!){~4glgt&C=f(!$;K!UYsgPcFc{;nU+y($_6igNL*qp5_Es*KM$qV2auW)V`!QhKdt~R_ zOuHx=5lF!b;CiQ-fS0r3DM8nLCe&eABAiT8Qb}3l5WsgI^us#+h*cxOgoJ;|B0^LEH@GbupKN$zXE+j^s$eKGd;nNne51Vf^K*S0 zw_0iRqm<_+PN!LHO-Qih{N_&WSku3OzU6Av&q|Nia4|^9SHJO%zkSe+aU_ zgA`MI{Yo84g7#udTITPsSxlZwNkt`ms+RN%`FVZV4REPF?$Bg*?v}TMN+Y!7FyHPn zFnk^w$^g?;f}s%K^v%`a8ULeK-Pv`xbvIL2%w^98D=_=ZgH6KD*r9+0-A@HzZjc>G z(2}btW$j_)SGGj=u<%n0mJYoD?7rLA=nXyyfd zn{)AJcl@mBBBbuto&~=uP)eD{onkq8`J*9&7Ax)-$nhAMyAM0asKXlRW%yWpJFf~z zmkC&Ju;0K)uGhD9@(%7ij$kP@8>F|RZ*99(_Zu-qaD(-Q?U#XdfiS;v$bF{xK1(yB zth0wRBq5HwWL7qX4pM)kv{WC4O&4#mIpC!mw2UmFnf(%g$1U1;3(|&H#JJ3nk8bqWp z$qfU5_A#Q}B)%%w-GBJ)rhm9Sn%kC)cy6-8xm_<`gi@U>GSFL3)?#b~oWM^%TSEXt z<_vpj2qqxWo9$!H%&|T4xv+MB9nO>>71-xiztw-Jh<^jd?*GKWaZ(B`r>%YQ| zHiiPrI{Vjr>U3aO=SK;n>o)JJ z5|o3}uaTxCWTNAh9)^Tfgww+c+wLr#ox(WqFy;+Jx!aB8CS8XnK>k)OR0hW4Jub^= zOaLrlx4A}IYUQk_9tLBYO&%%3z_g|>D!-UTP|H%j&!qkgN5^iO8WN~=i6JT5Pr>T3 zN5(ssmcDoi8y=exPYJWO_e6T*Y3+FTYr$ZV7I0TV;+cNwS3N%01Zt6VaM}f^}3q2}_1{4CAJUbiu zO_+nlVr>rY3EjPyDSsN`(2eCz)J;w4fc*GWN3zi?;1=zvO8;ZMTMh#24rFUUw$E5gp= zb-Hg$9R~cO_Ii4a+bDYd+ziTPVeqrAv9@n@HeCPEe*)E@jR=W?`}<~*jW;?mo)gGq zdUOYD`6H7cAs#C>#U0df2P1UW-kv*qEmd>to8h{w+`ou+mzFn!R&&VPpA{rfrUa%F zl^HH>4#;g?zM`K8T(g z1#U=9#3Gu;sOYDOKV-_AxkU9|)&|{0ND&hQc`ytj>~oD#2L*5Jp`@$B2Hs~Qzp&m> zrHW%y`(GbU&-G8MqGGuiiIa+2MJKcHZ_(mZA1>QH z)Wm;~%>*6xF}e+f4wh^NyrF00b2zm;7`3lcpg#2L{Y~ixBVm9S-L_`O7v}9E$7Q67 zqXW=-D(Q;3eSf*3ly$*@_5NYTc@mfA4iY9Aj>v~>L zw9?>BX)a$LoCU4M*POj=NKL|PmnWr8KZI|Dx&neMLPOhK|7a9zFh58I>5)6FG#*M4 z@mE)L*I|%HI0`5qQ)B;2Dr6Y?F7w0VHeC~*5FUA&uXkr{IiTSVL&#fFO|UNag0#Tt zWsQY>AhQGwI{Y86R~BY=8f=VFb0`30>%+&j9b|2rUo=OPh$EB410Ele+s_FT%xy)n zCK^p;3xt-h%|!S?@o=)ZX;Xwd&r(OM)Y1Nvh)^)V;k1oWLw9iNK}!*%&6L4g?*=}b zm7)=p=lMFMf_qqF7%d4Jvfv^LfjriTRH6P(;T8ZPn&JYn$^hxeLGyDxWf4j#dGh8o ze!1+rt~x~@HT~&Jq-B7?6HB#{*zi!`G+0#R^Y3x$2heeMzn$V>>TiG7)&449c+%FBp(QcDT|o+T zS`QDVl8f28JOHoj@v%#YSvs_{yL4XKJ8m@*8hD+_h&sc_`aG4ewO{Y~3$iXSPzkFawlD^L_ za;i9PbrAX`+$h8&A($Zg9LFu^Q>RukiIeg>^>YMV^U@(B9;$-|y&S9cTZztnUsZHl ze5Cdl>nN*!vJZIvGzyae$3qwsL?aFbTq2{+hU-~hUfR@(pqkP0uP5$N$$>`J|EfWQ z=W+m32~L|b83oZmjDff5*@>RIKCQD}``bL4>-k`>G%>w#}Brv&HaQ6xUN{96-hc z*CA@SjNsQXu3kz}koFPYCpaM$?J!8;Q6nB=3c1@%EQCoRLvl!CAF9$}Q`kY`4C^~u z5u|vS0sml589;@{&lGX#`K!P2$VfS`_D6{-HRSyH)2SUIkZ2Q2` zgX4s%&5(>>gI;?m_3^uR?ygqG=7annBBUqJ^Nx4Ux}Hh-XWi`EE9aU=O7bE3jN8^W}TKY6m1t!V23| zc(U{hNp7+ytSUS^wve;Euhl~jl-T6hDl<@(z#oz!{a2(dL(veI?>W1w;0UV?NlNFC zgA?btL~jR5qha^jDVA6b!tQ$>4}O?yA7O5`Itb(jxS<)Z^`JpIU}2f(=6?)iTE)0Q zFRb4F@e6sF?>T5S|sI_|a zU9Kz^$XR)q{iW>#oGTsh&KF_Gtvn1qSlV5Nq|{ET>Oy|R{So8`KOzV%Tnck(*ilB$ zSk#}J2hA_2`o`tZ>di0xp`=a|nM`OYA@A*<8yO|Jf^aG^j}Ia4P#Fl_Cg$O1 zo><5)BrKUIPKbrnIjdnilyFEO!=Tea$sy{p8%yTp)$p|`QTKd|gDWo_*qx=(+v_uu z#&@6LWik48&C$(qL}6m=-5y5M<-~Wzd#)PKf;U9f8q2+A)8Dttb0Avc1Q_K1s>8?=#R(jsz^%}d}?*uQXmxivc$33BJ|lv`%ihOVLS-_PwuG>r=|A0 z*h{x5a@yP!SPnk~6$&gZc(ZD>m=sGmySm`;Qp=$;+;P(nARQ*<1-`BKS~d4Sz;XfG zNIXdyHv1xMl+Qpxaf92v`2O`Hh+ERzk96`#cN&2^+trHYq6Ai(f_*SgJ2bbzuqV8kis;jK}bOwYl$p31=vYoJL)&oUK)E5 zYQH@=o=aCPJSp@Pr35P|$e=D3#I4US0!-vf3O?N-6*iMf0IOi6C7K8o@yOd?OQRYU z7*s38=Xhs7-ePhLeo`{?+~pGLqx^Vz`v=sUfJw`z^Jg7+tEg-W#cT zi;dSdO9zyQT}=UT|C7ALD~D&td)YdTua1YyuEm`e#x@@Yk}&9EK*V#cbz2bu$9S z1<=Qu2BnAiWE`4RwLV9q-tk5~8DN}pD}ZY9Pjm(Y$%nD%H2pIX zJ&J4`Vg1ZG0Z#b-fq@qP1|roU4O`{1#3;SQ6K)*EaG7tfKDWcvEY8X$_4a>Z9XDDC zWz9$jjUwX&@R+je(RA3u&5+?$%gf0{ml6sg`OK*iDUITpTF*h%2iGZ? z*A>B}rzI&V$`0RT5#ESc_IYGTp89x^yIDg&rQaJ?r8M*Lg)#Hbk#~HuNa6ndy<8|- z2LM3_N6G^k;2>nDHD;UG5cu_5R}WK8`(te1@dFR<2#_B9Q|mV*byoa-JzK9|yPZ|_ z_H&|^?g*|UP^$`?DO@sqN)O(K3C{`Yb0N8OT#`IM!wZ~T!om^1BA-kzIGcs$O!(-% zJOx!PXlJ4d2?3@)Im{JwN>FV!_S7HuhUHsSht@3w4 zG96Gz$iop^t+yVf)J_r z)wsyIcr&stgkZzEcZys*xBbG&dmcraU7ClVxi=$0jK8A<2iEuzRF2S=ID|Xu4e5x+ z@`iucJrBr6-56V0K8$6 zD6yPVeD4Dv_JmWvd$ZL&1T$L>>s`;f9`wM#_g*;UXXjIKe>^O)E5WVjgRHrY4fL67 zsG#R|JSDIbCtygv#LtW{!E2^RUE=r2(!tf1NaF1=aChf&m-D)~no{e)30Rq}OJ@|o zRSJ|9VQz16QDnI2KF!#Lm;%y0x@$n=uQ-w7)LG&pcP?ywB+{->;zdM4ij{++QBc%E zjKXrFV&xj_Ul0Ttr2V9B3^i_Q>?hMn?>h#&Pm3XDt>?2B&enckV6x+CLg_JdHJD2kmTpCZlOPxFTENCyGL+aO z{<2es1zC0K0#2uLzDH|+qMm{h$fXgyw8^{}Ngy=W@SjPPyBT*#v5Qp;oS1JU$CChmVcgLF&{ zu^}(NhE{}-+U`ylXJ_#=z6e{Joz8~?21NJ$E5dB}on^S4p`c|hMRj+NkAXv2#*b8VW-vQf^<>2x`x`jYRIX>YfA>?ht{~xQ4I5M}%yD9~ zwNsAFNXF^HmxkKjLow^#X4|{s&_TI3+7Lh{x)J#riDj|81-DXfCGrM=DyZv+0@My& zI0l&8Iy%=sQbkz-f7W{J<>`a_?1j%{Ir=}w%#?;lH80`$oEZU5t+GQ+TsMzB+-(HD z6b&l0oB0R1Z7DK}j}fCAA@DtQwel~yiy3y?+og-(Yj21(*^!30{`IlNH=RlvwslmL z`S`tb1TWX;jQ#YIa=vZIm#E6NA>Ve+WUITL=n5uxFgNf(gA&`jtfPcDy;9277t}_Q|mZI0ybk zl4NzK`F_%i2qwMs__g0MCdp#Z^|TLXVF%y$`14jH_2X zb$ti0hFbJzuW}ad-y-}H(`|CZiN`>PJY3iw`7wmph38x(jlr|BAeyBsmT6GeC(d+W zZZs}U+BX69FoxxShsSD5eAcint7XlmZvQZ=jQA#Y{#;cjG2iBigx>_&IW+Qw17*0q zbudyahy=c_yG=iy;~er#z>EChdQ8DT!|On?2(dU#LimAtVMqwox8+TWGY~a`r64b< zO4PFLit>K$xJ$jw_^l9XYkjCtOy!JPt=EYhwMIB~e%gO+;7wz|G!RQh*M&tc1++Gw zu4Yd!MS)7sP?pZZt{a+Giimgm0ctog{DjI?A;(``4YA^nX-t@O@Ysm|qNQrBmfyAU zd$vw`p|H|qeVNwQCQt+C+JHLD%w_XoXP)B(aMpF^`B@DFDVL4BsMDtMLZOlO2HR;zs3kO#^!1l6e;@_BkPE(Z`-yb5 zQ7aKtrZ!t=C#ut+qmip5FiZ)qdzq3>Q_(70R%HuR13AC|YAr8x`FZZ{X|SPxA~`}j ze*>)~Sp2s73k_$Q(hO z{D+}1p0tlW)phP4K7QS2Xl#K0Y$Fbu)vadnGDJ^^|F5nn(kDZ+d`-gZb4wdhHc-hS z$;^ifnXdIzC}{4ItGQUy)?QJDNFFbP2I>b`jJ~90r;Vj@tPc?l_0C-~eAYfypRO zL^6?IRYDwdFja9gWUE+qu~Ax+UoJM-cO*!*J_21$y@{NLms`R#kU=3j7gVKrPy3rQqShSBj%uQCC}7 zFd0p(Z9bw2L)Lge%%zS2n=Iuz+Ys{(jvkHyF{=#S#2@CO!fZWF;_A9Twq&AA!*0*O z9PJIX+jo|-+VCG0XN{i*2w^dH6nZKUlea>kQ5DnEq;N&LI8jz6UZ_M5c2AS|sXF&sa zWKuh{$RYO5Eyn|Hj%0%VcoKe&%2da6*?En~k<}0Ja z+`fBC{u-hM{ffx9yQO4%LEy$;p|hE)scIv zgojGdcl?b1xYP_1_IZ(YU$@ka7?`-jVe@fM5Gp;4d%vS}VUopnUOaHZ@4mWpqkJ~; zg=*h|FVL!|cSK1v(ED z$`4uuTK9w8DCm#Ww5rEd(epR5o6}O4?I+4mY7uc@!Ne^wk0q$Sv7}tCG@?HDKVcSc zhSnP2+H^gK`8eC9GI>Zz2y9-2%}=sx*3|>cZd=`MJQsOmp_)$PTs02Aw$Qvp&{t>x z_%CK^g(Xcq6c$=Q?!-n3mFkt@dbCy=&Z*$-pmR0CE)#$Fc>Y?apsa|zD1%jgga>L{ zC0!DI_%0}FQh4MuNFc_ zbbdCnYP;;9kHfIr??9Bh@_vx-n$&b&&VCJQtAQVtF0A5zmTBKj$t3?z)Wfw zLZ$sQwKL8HdzM^vCssM=&!58@?s4K-4S03{!NXR*=4jxGGF`NKW|ZQ&kMrgtz{Tht zp5Mf+CP0z-GI`L?orToEf8_BviU&WfIPY7WFHKhA8m|WM*^~B%-YH2~fz7{%3ntWX zSRMAvNL^9HZLC?5Dzu@X!?u$!JCanLXXvOINwfgZ&MiibcmS(~qNTsZ9Ng_C{bq5! zT*KEDcEOCtz_1WauCjwOp`3ydFd%(w57n2JbV%@M-^`j?EJso=1>^p4FM5tfvAcAru zr~K|9eY-_OK%9+7P%DH_WGC53`RMf zAbM5#Gfj@M%?JbyX3?`)%mCmJhcb5&BNH+Dsg6Fl{ZJ4o zx@17RLTgZR$FF{;k}~kQu2j@tKXnRTGP|nJJ7cMG+kI2*spotyrGudi#)1C2C_+w! z7aw>nr8zD(;?wSBwzv~3SV?mjG56? z^j3Xh%a7A8Pj~))pOhG}MAf*bFZLTLoQZRofBNTz9SpGmYxvwx=NE>h?+&J0^&b71BdL&Y1_R%3ai@>Ql!iKjbU=gC z$>*I=PT+iR^vsk`T&hcbIynMAO#8%R*qsBI$FD@Fnloiq4iG>u8sOGjl#M0Q4n4NY z1Kk)gu@1fxT}EHwvjdGv-yf7}ZzW#!`28w<71gw>B5=5gA2gK2Z{3`5}i zM8V<*U1z5nPaTHL3M8|dd_OY;RV!JYz8!q5?zr;hq;57NG4wqna%)9Hb=i$D+pC7U zFKT)v#lAe1@~x>7spNEX`#(Sa`bRCDqoV)#d9aaXalBIXd$dJ}J??(xz0}+U_Q;3I zz2Y=_IIC9CP{S%_CPLXscSn(n*L#_HL>`JmeWl^3-TtoLF9!g)zNNCjnBSibk!(wd zA+Q?fi5k}5k35y5)__?&WQ^pZ z9(Gmqsr@QHUb4D)E#=&5&#Vlq)>`(0EYh#`m0m_Su&miUgfy5^uzIb{Asi;H~XV)_3wuHXJ5*B=cN0oOM-QTL{?NuyGF3ar{H^K-D?!p zz^lt?HGhiQ!~9=WKVg>_!UrA%s(tn_X$jDVVN zou!iFn%x!EK75zDjsDD6YjB1sA|+A__Z=9YH8wgWX@JO#IG+naUUuuTqR%@2UjRr4 zxA@>(P+#9}7{WU389Ob2|Lt2}3U7)rUC3qN*e?I+-Cw@LbKSdK+otHeXU&9~T=!Hz zJ5@(S?PS)Zu0}GMSZx@uYms288iiwQ-do3FxPR&^PDd~ z^WeAc@SGfXN(C^+G=S5h-f6Br^KsiJK3{emP%51!yI53G>yH_h`InXzOES)t)~6?z zXI8wXPw7~tBvcTf(&A=@(=sKut!48wWyw=~MMO#~&0NPW`kwc{4PktTTQTMovYIij zi3kt=?aXEF#BoMJgYD0*!^%rv4Ph84=JQCTQsn#o259+`l6*HhbkR*YLBniqU%agCdl`xuX}>`xc(SPMC7|3^Bj9!5Cm^iQa;Cp!F}ITL${(|HCdDAk4;6|XdIwdpcWaFGrR9jo8 zG)$9~^0}gGd!ONkDm1$XxVzg?`dq>@`E!5E(}b^;-|gJq%hw}q)&B| zA`pDfz}}?%dW<~U->^+ec7wEl}I6w zK4(NbJl}m7S|^lL7c$Nm5mx}61wZzQD0Mo;r-lVn)ykdhFRr;t9n~_-__hfOAgz^R zp6zPibMNt``}+)R0~m4BjA@Y8rBc`_?}59&JhSu8aGZ4t{>eB0B6={wkH7wBn7yD^ z5@SLJe!oWWQQ!Ao?-UDLeAi}LD@LaskvcgWp2-L!M0^TaTsD8G#gIXoLvg89owFH5&$q~ z`C9mPpsaX|HMMoXE)@5sV(I&Aw=kpvxmrUva>hZSy*C4jKKt!|C~|8~Y?nWSh9^@E zg)oth0?%W<>-77c`vow3TuW?eXlp0wJ4|{?miz#_H~el=qTE@1wsUson(^ij{V!O_ z7#!C|%!;#gyg`lShrtvrAi>|oh4>RL%yz>tK#WTwt+jr+yU_#CDr&uS@~(4}wh{$8 z4q^)d3MR0L_{514TkWc>#X*{Syc4&&GIJ04|Sr|LGT_uIL7N zfyfI*WqK71i-iuOmG?M9D2&^$8$Aa);10If8&b)fcyUX&$VHi0XIz+Po~Lt zoiPpij8wsuj$QhwT`c{|EtQz(x?F2bQNo=@=>9zGJi4~+QS9OD2gMCNom;;K94UssX&L-~ul*1=o&ueDd>^rQ5>X zXW~jr2@Q~yvQ#mcT&T+TUi z(r6xb+mi@&ws#ZXxtK%3a*G7(4OX?dSCq6v~5piykY>?j@v%*IsEin zcj2~Ae2xL&u4F!!E;Iw2LNT6fNMFO4xCOvvT=4mvi)L0%iw*On!bXwRCJEe)ToC0j%YKUfN7XmbpD0d^WuifH1rw* zRxr-bV~j5#Mzg~}JgXFXu}b7K`R`UDmW;8ZzD8zfYH`@ z-}&15!+-o)NZ*4~Dxqao54rgQBhJ)=cYj6S@OvM{WB>T~uk=(_#{VdQCyv`b@wxIx zM#M-MhH@Q8hgA&{jEN?#RJ&5LjffU1#5fYw@T6-IkrUb%WI004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00SmTL_t(|+U?uBZqz^&#_>ay6v06vfk0^JXreSg*cL?6 za=!=y;T`yTN*byxgeQP-6(lSrLdtHq^^4#MxF=90LfWBNHhc4|XU-gZ$I{4MBwPQV zbYhR~HG%TIQ2+!$00ck)1V8`;K$d_5DB?5zLiK6e#3w9Zk_Xs^8+}Y~-D;oJz?|RtNdV8sCmB%kJ-;{Ls{i@D0DGC^vyS6<%i6Po8T^q0 zykO=Z24R>%s9-w?1^ku)@Wy8ihyL$xOQ`1)2uJJ)lH=3Nwe;pCp24egp74rP~b?f>VASa4E$*rX+%6J``aLKU2Nw>qT(Rhax;n z{k~O45Xgrj+)Eu$Nf)paU;2_SHbR6GeaaX5CngZ?r<7kdIE_Z`r_r>=sBCy4i`*|{ zgp(QN?}+_tPaAK-y-{;(!rh|sMFG7gBGM-@pjxqA} zgc-NT>knD)W~LbV@q~i;tEPCsC~J(`38wkogo4eRGTj2}Eb|T7)K>mG=3QIC?nR$| zjrhocV4L406l~v9>H(wp&Lm7T^6LcA*Q|td`F_#OA0v+dyO?9-(a&wp=N&+JoOgid zV`TEw;6mO3geQ3i?8c9L|L~P?(NjRx{Kwx005u}1^@s6i_d2*00009a7bBm001mY z001mY0i`{bsQ>@~8FWQhbW?9;ba!ELWdK2BZ(?O2No`?gWm08fWO;GPWjp`?KqN^- zK~#9!>|F$)we7|1&TiJRY z&R~EGZ0Tt!f%no=Q~B=*YLFN}S{nW>^UsRCvUfj@|7ZB|&O5(<_1h-__&En0lh_gl z>~u+D^V}W}xDaGcmj`-XPH^K-x7Qk>vVBOb0Cuu0E5FMULcgi+Lr)G z%$b&w%6}qAJ3U^{I{avp!|8hVzMtICbP|9k0@FSA>@_xKyH2vPb4ZMKb@w3Xkm=m6 zkaQ*YBLMZ^kdYNrP<*FAc4h{oAWO<#`F)SfvS1`)I1~VN?1&;eGd&HmGSeZ&X75DZ@g)@3zrX7T*A$-w zV1KinWcvx~_H(;YtafyDqfX}v4_Haef-+d2kC-thD+4mq(&*3aUhqD2BD3NN3BY^* z@427ZCHw6juX{*aXE$^s+o6aG3Yb_}Fqr_Te1wJ+WTdAEI#9Rk?sbGMGW%ByN2dpdOVR0RsgReI38|?mHD0gx_a_n%PY3`K@KE=; z-Qjc&X+tp^%pZ3mv0%IasBCFywB%=JolrnbY5*eeE}S z4g<90{4UJRA=l1!9J{;j`oXntp9Ek88~N~3?DofOl5{BnudAmwDnNtMo-!q2lF7|MN9kef}RzxPp<`9`v^ zVNw9_{>Puc&Tg~+y|tq=i>%vN7%pC7xUa0?z5<|fu3WU1^KvpfWv`qRY}l^={K0*H zD;PCo;Dab;uWlyRbGzdLu+P01jsbw24O3E}C_fi$5_{6=a^KpYY}juAy!Wx^rrd8)4G2Gvla5(2F%*%nC%#6bxkN1-P3hPPzanidVU44z+Zrjn+*2cld zp9Hg~gR#(0P#F6dz5^#W+DPg&w6slT670P5;Z-ZX5CFXAvFBE%*zKztTH3REdYvax z5S84D-=Qd)RNK&!?Umt~-#)zRj?c9S{{0hcUU#`Ymo&Gx``jyXc+w>rJuhN0{5f{T zgk$iKyQU~Fm!c`JuDt1*<)146-v58k4`h<`qSNU*y9vP;Y#{p$fc*%KlH?a6B2*{@O7%-vmDryW@+C5nk63y zr9^|T8^ISTfw4Yemy=sEfKW;__)cyoLD;7NBy0DJ2|hUvuOtUyO3DjK4ZuG<`NHcR z-Q8z@zQEU)KuKOuauBw6oOkELt6ok@0N(r9b9ePPoR^&V;7c|DB+vB_2)lY6%i|d_ ziHjfbp2wcM#_N?=Ax)cFTe7L^&)WHy2xoL~KcH-Rq2||O+({1Gzb6~4(&$sNSor%x z=yO@$vHtr-`FW6$mU4AG(z#*=AZ4{kjBRgdZOgXGUXBEOeG5Q2yTOG89r)+mdw+a=Nxz9;%ICA$q&FH{+oK0xINynDv?e@p zCd$3;CW?> z2Sxyf76|^A!c^Lq*K5Y2vf;a63asz|+24%V%iE#!+z?AHVN^OGFPpU4%Ilh2v!f0v z#3>ak_&&1b=d!31nJ9R&<|C&3Evp)r9MB}A*Z+KPGu@|wb1}31nc*|7qvn4LJc!9fem$x9tYpEdOY>`3{<8x zzt(UWG4X8J)Y_h7lceYSgaJwPc6IkYsZ_67JZupIFTU(2o9-jaRvhR1PCJZ)|DH?hCGp4uHYIXXb*T&juCb!IZJ1VbbVf zFm}Wc$ji!#>Uzvl$4FPIZEArdmDO;t>=^7nQXcgFm29B^%QABj%!K!46Ca}yJS-sq zj8+ueBNM&$#|V6Kn1mOLB#u`_oN>_9KL#S`=1!ReUz$D<1{D_QjvKA($LxF5G-%Aw zK`?gMU|6zX4u9`pSq1DaE{E-V4?-u89%*znrq?Z*!dy(fel2q{rWLhJ)mxd>s3#^D zh(z(=!u&_@*DK=AfGhv`%rcMH`&L_LS42`bQQ!-Q{S=>-sG^@a=Ttar?(_iY`l~cm zVao#q#=~W>arhnlt67?8I7oNUO+OusnXhDc#EU`mR8 zWot`I&`XY8!-xfZ5EOh_8ELR|;aoU<<}}Vop;*rA^`hu>fWzPFw?`A>?(Xh3vRqmk z*ld!Y@zOxT-?K6^NowHOr3Af)Q^t;hspCe&W#=w}4|nZ_4|ne2x}(8LW-9xsAoTqr z`)j-ygD{yeF*^zPAPH5QvPKKjI9SV6=#=7YunUVu}cNdZ?duL2mYsfA_=d z{vH4Miplt@_1J4I!*#XQclUfBk#R40zw; z&;Puoz4I?-T}fjFVl=u}D7TC9@lU^XDU8DjH`|}YYcr}7ZS5V<($)b-klA+ZI|Q{2 zO&~D@9Dz<^p!EfNWv}2R)`L%X4H^fs&sab}q8KMp?)AE1=A;QQb;4K}KWaFFF#`sm zE}EB{%bC(_jLEfg_rp&^BVI4a0>f$aL-oAO$b_ZPokF(5xTBnyUQK$pvX zt5r)}WpX10VmvKOvw<5@RDf3JV8i|dyvC+x1YR2)sjPu*yZ1q9Wi?K`4Q!~VrKG0o z9L!AX3Xuiy=^Z8dcVy7{@5ukgB-8~Z8tybWc(fY!9jO41%L%7WnE-R9PKL1~hCyCl z9t<2%sM8tg9x_nBoHJ!StlPGWqlzC-VK~_bLNESfGSe|(9~!_$*OSvp*a^vkK>5Oitj`e1>qfs(8_H#2-} z`LdhrPw(b_m)&ZVRi;AJ>xW(GH#3ki1m$);Blfs)Y}WGc)eU3kQJ6w zLCBrZ{@?XDl!NGei9=+2SNHHc{_)h0g0Wyw0J`0t+myb{{kcWr@1uRI>KdT9yaKJ& z9!N*_AjYe%YlK(d-3YB{!4liq?WqEgSRk>K2>=U7T3I_`)}33K;1rexSAklV$~YKP z&}eCQy5XgFH=*J3UpRB#ESNQQ66EA$Loe!66*aZ6ZtD(yY)5Yoq@`zY5Q4(4if*X6 zG@m$T8j&pr5yf$zv9MXtQU|>2k!P+(_ItX&0T>*7vW43PL4f+!v@ydV9l>^_ss_rc zYQat`SNe%bR{R!zF~%dXvaE|i&@x%$M-PWF!-jHQZRD`QXyIi9&M|yY5qH0k;iA2q zLkNEE-zzDD;|g#dB+G8|*EuK#-;dOkC0xi3#a8J~?Ko)#9A^@)Z=hL4wwROxt5y7W?&Fyx9qpOpr zS~0X<75&T%eDojbj?*TOhpFSoz=YAGe6imM+1NA`hb&fl>=+zGAnhwY0wony*5a9| zp*D}l4K|ydDh)NNfDce)o%=v21;ED`n1e8&GX?`8Bm{u84b0E`_zyq7@$|3(NNcZk z^*9c7_4Fjf?Ghi6YYjj}l*&Hy6?(h7z=PJO8TbYv=oXwh4Njjk1BM`@B~q_d#<3Nx z+0PD^z=mzxp_GHscw2m5W`Ps|gk0_OGob;5Obq4et>$InCE?(Ea% z@E3vcy)B=>`put!FT;rGR8!_>H*IDN&)mVeAlHu5;ov%?bqb84P_TaM20_(yc+a=t zuQ!IpfWLa^@%E0co}5U;OyBwg_3OgPGMKZUta(rNLQi)moI8IGTzt+MT)c)luQnX# zL|xdyv%h67Qt2~s%gFHQhh)9d5~~@=P81cf3dL#1la9F;#q;xTz6-~jnhpLu*(3tO z{f3+=NheJ6`z{3*lvOrVLvX=H0Qy8UQ$;cvCaFFUs)mUuewhgXgPB9Ii{F5#m25D$sK8*17(_5yy(_{6Uoapk zgPAC?W)+STUBhHhd~L}h7&M^JQp^(A+S=QpqoWfj+o!s=4)&FnL2+3nXFZ04#~4#5 zm+N4@`^(76f{NpffpeGF)bT6uq5(myOJZ^o0(9c2kuYiOD6|OkxO*ihJI4S<<=RLz zFPJ$E-rTSm{{7xYP{V72uI3lTjD8{`yYO05k#V^rh^Yn^3jkM9OZw6!<2MMs9&*WC zjlVu?$$(U5nUk5(+?BN54fFsC#(r7pX>jSei(ui*sg@4`h2k2UnxLzvo4ZUXH?Io8 z#N)px)^tq8)bq1obr!lL1@$51v0(&dVNMpDI%N{fJY^ybEG&ThygVpGmNXO71ooQx zMtJ1;SNxVUid}`>{GZEANyqZNXTyiZrxQv65-_-BG%RSILCxaMfVAbJ1jx)tFVZHg z>KJfPLEhCZ?Ol+pTd?9IZ7^fZupw~c@-JJ8TLpNP)wS^HpddD@LMT<<1bhlm?09UzSEs`bAMM-=8~^($3?DcE&YV9RPMI*)G7L8q_qqG0--DO_ z^FFfSCgW6VSdfrJF>7MNl+?7qMFo-;KwrY%*G=juNg*dY^D3=lS_eR{ET7@Vr!?6* zlFG7qA&zz^WxuX>)|^w}ilt|oid)j}>KYoLvgSB!_;?4D9;@d1Sz1QAf->-$fGXex z2*wP|nn9Sr4mP`uzv|mMV9kFvpuU(1OBXGKsT0OPQPBVxP*7kH%~yQ+JeV?mH2nRU zmjb#WXJf{6Nq;`JB@m)@oRa2?n8E*{Fhss6cynjOkpdfW>ZY@cjWH(ybwEFosmYolyhYy9J zLk4rBMHI=kD3bsD_;YxkM!&AdbiP3d_yZXi*JK_0oYksdP!Yqj259C}&Eg6`zfZ9J zp$E*XrSTP4u=zd|L7_xuB_J25A)|OYJg9fKB<14Py``Z zrf($hK&)1n#N#5$p66J0Mj_(yI!`YDEK zS&jw1GZ>ZsZ!B8^UpjSKLS0A7nC&}U3U6=R0-qf$Hf0#*53M;Jy>RxtS+HdBX)t=^ z2t)QX2jRb-hGJw}h4~aw%mlWhOm5N%^&kX&cn-{pm{?rFM_Yb&*4Dq=deb5u0B`&2 zV{&I;DTUYvIX;eOeFd-RcDd!6D|or12)>GARj>yq_vLk)(7H_n#k7zEZ3cYc8-fb| zP2XIO;&pgf*XTm^gnXnfmm8I7eUff!N-B?j5bFf1`OuCLiRqWuz7L-r(619n?avh5 z)`RDx%9pLm~p{VYV44Bx)YXmBXtd*i(iVdsHTv^4FY+O=wUjtZ5oLh*Ly{5e5^ zN0E^B&Q9p*>EXGBv^BM~La)>3sBdrYfF86q2Nx9uE^hdcfq}3bJrL%rcaPIY@~ zN+82V^$QwVFi`;1>VbBjH70#Y1fTYhtj~c5LEJ7fT)6N{*5G^pBOmyXjkFDi6!4us z=Tx}<@{4(l)(m*Wa?J=bs)p(b-kvTazP?(=*T7)!X@&vg5mLI~K8$NEPA>CKvZM(?OQN=MBikOVU63S;7nRFwp zIh?MU_^S_qC{m_{i*1%r(1qTnjUNrm&YW)mj{?3=4;=}BPiCscLbQLw*S-StW=u8p z5mGpg>P4vL_h4x`>@O*U-G@rJ;leB7@TpD9$TE8N0!ggu*5Po$PE-N5?>&V2rw4`) zE`s?pr*Xq3KR1^@)q21#U(Il z+*lo$hoZIo(`&vC53YJaWl{eDd?1tpmrY#b0s=rQib(TYSmW=D?$O3Eu>6zaYM2Mo~Z+#`kzf;xPN zj@8x+zCkG00x!Iqu7;e$_hpn)NC zz}Qo41}C@a1Ozi$m_8Io$0coa$AJ=jSoXoPb54i3(@!z@`sf<-b93O=-@gv-d*mr7 zIa;Y_TLjbbrgm8S(I?z7K5oot-RGoUP!puOMtw?GKom=|#>Xe)1L$MIK#>WLcPE|^ z5&Y9R1Evg{bb3cm@3+nJ+oAzuyn(08F^RED&sk(J680QC3~Szb9~n;y)C?&J2@^(* zfZGs!RG2PCMvhh2z!99>Xa4;k*o}-wWs|z_S4`jwc2g1pJOxTCt6(pR_-r(YQc%QG z#{?}K+L1jM&Yr>b(TbWn9Xk;SYtUfHPEUa$0|)5zLHZu$FjDoO&)26$NR%vVC0$GM zmx56In35O_=~`gyz!fk0q2b%UzxZ7{4)g$*%N3G+`V5S+`Gb&+AR_Tqke{zHI;v4| z-vTbLW_e1^|ia=h)?Ppp-}tVRXbV zfx+KbywL7+yU&VL6vh>hVfqK_TH3hJi^L{%7-&Ie_|HclbH!Y(!Ik|6M-vG4?f<( z9nN|UM;$kcNq@9!FBi4fqmd-KSqOw&9RKslKuVU9&u6ozz$@==M7^<)>w{!0Q4^)= zhGzY>awAyl8&pLt(>%0prxW$w)IL+bdU~B_+7>Qdc3!X3wJ6zFkEF&()wQ(l*NLvr^|zYBRnMVkV?CtrROj@H!sjTQ6l{KX64`sH7N-C@%+cbEs!bnog|F#)n|je|Le?pDe6o z=Jev& z*4e!vS)9*cQP9onJUm|L?dk-l(}9zcqALJbe%`SBViak!bqu9|k6ajaEgkB&1Qw$t z(d8GM$3<t?ljb=$h9wkq=p5T{MF5_nWS~3}#N7YKVH2p>gp)e|^NzFOYH- zJ#H7LIy)K^YORQvsGugYngUR%AEC|-DQRKofpSa$%*o2!Vq37};u~CU_sDp-R}u)t zz`)s&S;)`lb$NUjsBxe!oHZQ*bdJG6eW>(^5q!dS<5iczw=VfI6y)VtS#8kO)eUVO z9sd3@?S_{NLEuKFOGP8YjOi#cLX0?h>?rsYjVCelh`vMW?7@;Um^5}Ih{W*c8Q}P;OrDTAqppzBQ>43=mKvV%JYwC&R025jPF!l#D#cr!4BV<&6 zI-sM`DyXf7oPmn7KX%Q#7@#$I?Pbd>9Mm5|eM`Hp=v!ewa%J3!B74}NBFpw|t?h06 zXCvyJ^^Gl@$x14#bQewHHXp$|de{(dppc`P((Ut1bVrJO{OR@|!(X0w9`(cu0|?2H z{lrUe!jG=Lg1cEHO=0@Q=biyuKlNqQ1lovzzV<_3HX<7z2H8Zm3e6FSRViXZjG7>? z8f~N5&3}4C?shm5?tBg(aI$ImP5oOfy(FdC+1tzCBc1Kcc{6oKYG`VLw-9{qZ`-9h zlVw%>Qul*j-*hb+E5<>l$Vlz+I%sNbgYA3v!(J4d)E-T7Osh2`)OyWd2|?Q$iN~PSX5*HBG=zgul4!*Dhwk_3^aT4c)0rVi(%N1!Mg8}pYJyhJmOAxE?u zC0o+q4b0Dl?_6<-A;Piez#&-k&IZ-s&^&rp;3H8>#?Fz7WAN^#t+3|p^>D1Nk-K`x z>ZEf@l2Y)aW;Rm>3t6F*YEQmCTB?C^|0=4hVPIh%k9=fhX7I>^=<6e&oiFOabQ4|E z&;*0=-i8ky%*DF05yKuq2By9({2j7he7rRHQJ(%hESwv z!THNplHMY^2a<9#!6yM?#l=gxpH90Gh@SrUJJ92F8xBEY_r6HQutR zVvxmt1{LPR-071I;&bcHJx~`|AV$C4`Qz`Hf{!d^DhPk={g1$dEM}AJ7MCZ7eILY* zsp%Q8Zp#jM__@WpnfOS8;%fO1}%mI~7og-4Ywbci>fWT0}wm6O6hA{X%S)oW0O+jZ_1&e(O#(OIWjg^O6f;*x-St;Ctf5 z*O755)ZT$1z(-6vbYPLeh%k+OARE>+^LXVBkQ5 zq@=8p*8wZB?Et98jNg}&&8_gx2V1zS zNV^pvGcH}cz;NfpIQtKmf|ximbIL?hjx%&{kSzF-SrMhs)&s;&5s`^wvtc9qL-Kk& zCji@l>7v);Mhr0+ABT`xn8`9%mz{r>0X&t})vyjlo0-_9l=_G6_ytU`2+NV^rtE2Y znPn3xZ8d)N{p(=Dm{G=wMGN=CU3;M9$WcS9w@c4E(HD zw{k1fjQJ=7MXVbkpinkWDe8=eOGSvSs(Y&>33p6$B zvsVi8bGa@k)=su|cAKVoEtok~!%@*&yZUa@EIcPg7$6J=F?(`?PZbYV7e8gRu~R`Q zwNrC>41@KRa~2sxbXC=`ecvIT)F8^F+rE3P&R6F%GJ1Kf@zJVUp8ij%Rz3dJqbO=Y zUG1k$9LMWEh7B2_OC(U-EVupPAyC#?R+y!%>Ns!hakk*+n>%d^8Y6qu8Du`uO#qxf zTOaY6JRZUQUijdXLo6U?1fLuf$O;IfF-($_0Yh*3ImS}Ql4HkA2A&1_)5ylxpVr_HLt>tKnAHZF7hOBB0hM=Rj*7vDgT9fQ>LbW`@= z>wToA!O@xq4#JA6DuZr#-oh_IZdQh2V)m6BHLSo%nKP5^}H`k+1*lf@Tmn>RfI0m_84j-v79Bt~P zaRw|n+#cxaGWKwsb;`t$fC(n;%)uvz4L&m_3s$iG`eakQI}JDsi+~BdDBlZ?7R11F<--%5)G`!L+x6s`1+_-@5eiL8;lZP z_>AekqrGHhDqt!X6Xh29EEi&-QQGrj@aYKU;ujxYxt(T5?04A1K42E*> z0l|Zh9zM({f@w8!ld&5)Y>2_vSB6TzxDojNcE>MxCj$!Y-G9puc%vin8mdv$cWc&L z(fS9)Ywj^w5{<%%X8z&8(qNixpr{9i1wM!?^N9vlC=kjheI?l|e|F;8z%p2ulIp!- zDR$#PwVK3fX>&UD$~~n+wX_?b&+|^38}Jb-k&p8h%`;32F=vGdfGLB=Y!bzL60b-3 zj@={_XufZ>Vpom{eByl2l4E8YlHZ32KySI1?+|ACNk2QMm1`82lAy%|WfNDd!$bt9 zrKOpkK?DFL3NVvUfLZj&CFC7ihq%lqv8lbC8%J99ZEI^Y{Z6{c^^0ztVC)xfolgXx zaRm({2+4Pf)730J^QYf#)ho&}giONgBfzQsq2*|gVDOpIW|yRhxI#1B2>e@V;qiQ`Pviq<%RPmVA6EI{bN zhxmmDez#5Xp;6u+9`hs$l%dWcMku8oobm!f3&=lDX0H$eP!9!^*cdd?3m)G;+ASN4;giu_}6H4Bk>$G7ov4b zo$a*>xWWX~oLm%-Gf$bs`%wp)^C{PrmJYiPl*0Otcf!%ydS6d#P0a`Gx#aYD1`$k6 zG>Zkp#Mrq(N~qWk&+q5E2?8NvZf^j9_{P2y%?zPlEaEoQ#1-K&Nleu?Dc82kp!Gw< zEyLfTKnreaohDkhwBOL8BEux?DJe6Arx%1CUUt5+f>w{ z^gWk+gF|({;zs(G$g0#Kwc=QfVc!L_rW)R(L0c8K(uCV)cHR4}5q1 z_(mR0(Qb{+&9HUX-hKyPs0fj0fOx&`&jJ8^`VYTZOAgy)55;M(Psv2lZbbtM4DU6g zek4x5Hq%9G<%*#*+Nj{%Y%q(T~;FIOZK#V5h zwtz=MRqEYTh_EoS6GY#kEmmNgG2kQ$b7_+ZW$HfQg66h1L!%$6-S9ODk^}cqTv^4_ z^mRS@($mop8I6pmPXwiSELEz_o-s`asAcCYhT^BzfVfy8WnR-Figazmh7QqvhnkFE zhis@an?u(fODqK-@Re6o!fWd{L2YvzH!!rls+n+Z>v@yIp3r!K&u>5mie9uWLEn!i zJ-^SwZ^A~|DPjr}7ng!@O#@d|Q)l$s31K(BuVjNmM~)cYKWF|d!(`CDR173C=4rF0 ztM$5?xgaB9)oW|v2m+_7rp6$?wPmo{D2u4mk=Q*_eiUAL_XF5*xYP$e6KhuoJQl9} z4F$e%*(TV^>s5rV zT8~zzc2G(yYk6X+Ru3fRAZA;?Wt;B%EuEdPbKe2D@a!{mJz*|A|4dl7d7I%HH}Bj7 zv!+ahoa}7ff`X^boCX&xo)7PS^oe0=sQDu~njd-oHJ<)IZ^jfDj23)RLB8Smlsa~> ztOE8ND#dH-;?8QNQIJyG5nfh{5oEH4HN~TZ((R4x7aDwA{CeD*EdgjxOJD7FyZ)wR zTd;m^QK^q>=<8Cc*>(j^BI#gSqk;N5i`7h&m2&V%IgA;l@54zwWqbhCbh^Ut?mAEc zr_Y_O>q1b7zh7|10^R~w=jTHfeD&RpeDN7tP*8NU(6-JGLQ&tOo-q}uN|aUA0Cgy% zq*99Tj2=E1Dyr*vM+DxKGbJUkn!D)lV_?E!LRo@XS8nO1VTl8uU;NVjJgf7mYX5lh z?q9SbLf;j2BeQ?;1zC9KkcB}zitIpEs~BZiG<&+v3Z!Fwil7p8E-LCC8b}OKZOFYP zW!%EmibR_5uP!;$7eW&T{|JI*^Nvp~eSIsmI-xjW$g;N7G)Sf5rp#=}%*lm@_8$H- zEhCeIRI=F($F;I>6|u{hwfI$+Vqg*Id@O3Q8vvloz5c}q?`^aMAXk)KuGQgK!*ZPd zMGjbXb0S;N>gVD%2gR_`8B|#jeQ#+QG&D6DCh{8>FEP=5Z1CRJ?Z$fD+#I-c$yvtz zkzrroumuhjmqLA`@f?)2de6-_Y5aUj-7`edrApSZ7Xubl?27AtXwVo0nBlS?i{QG+ z`t__9-!}m8**~vb?REF3HOwONIUYV*rE`Ulwdm;SHZTCiSGVsuV6bpcn>pRjg3Mn_ z;`<(1nZ>Aki8+Gg=kqz6^=U6u6H9KE!wB@6+T#Y$j2|@|?!VRIr7Lu{B>?d?rlpsu08u>a3)_@*j~WwmaW+${fk@lEdQ zBM^!r+}AT^5&Y}k|AF89;5r`t;OY56KuDhlD&D}NdjSjf!0N%O^XY}D^Ti52J~nRG zi>B9Tmb7^FZNHm@hR9)iYHB~hO18Lhe}8CAq=N+6*g*ww?)+K2!6PjS_|~Rv;PT3v ztQUoEys-S-#c;vdi*<$z0q(aCJnCC{TzfXJ2flLF0-IoTS0~=2gS$@bsMKqlk!wMimzl=bkJDPLw9y(Izj^Rc!|78{M*Mii*I@dTNjf0W zq<*q{Km7H{=S_LR<#g~~f=iL*1`QYhLk15rEUFXfw(Zls@X#|a8yb?E0gwe6K#MJW zHNce2+p!t*{8-fBqpvu+yFYp1uXmebNeaTbfAdffzb`ZN!^l7 z{1$+rQ#|jj%_m!E&u-j~;I_%4d!oTQD2F#P~CfA@NAZli1>@~Ug=Va9|pFrctNCkCmD0vReD zWYlQJyAwf69Z!c1EQCA+y0}I!IlpNs2d6IU#&ou$yEJj0U{kPh>I$!7yMX|O4xf$!Jx*r4eSM{^Ns+l3!PYnXFs3*zU(^K=}gZEq; zF#u_M`P~m(Xgz*e>hs8ml~@2m(5p_A8 zFnVO5`#fkCiYA?p)~Opdisd{+-~P@zW(L&KC#?2RCg>iNUGDbvWNAn9tc4et?~cxf>&dp$mI0oEntWjkpSci6XQCTESf*j~;#7CFJ-Ye@r zgrNfm!jwr9bjx8;k|`7zbYZsnpu!@OQ{)h$y zPg-UAS_=;{>pTS6CFh-CSY%quNc)OQ;jN8ZQU5Cm%fYMzpLRyI218Kx)98R2(JShF z!PqZqy-(OZ9QF&T11cLbg#&NmqC_m$WDYxXb`$Sbrwf`6C<`pB#mhMP>Oh(`({QvY zWj{YV6AE*4AUj*%^pd)i(#n*i*C|V1F~8MK->X2Kj|>w!hX_L21{V#0#dBxE>9c1* zRz?OdXz%RqNhtXA?8p2mRaO9Gg5=P?$D#%wsXuPFE2!=l5d+fpty}N@Bt0u@e%~(V zB?oZ}$ifd1gv8a;{@t685GCB1&=paF0_Evh|lR)AF{9Uuhov0z4r z{yRazX9(pn3ukj|)8p!OkZbAga1RkS09XI%w`<)_=PUhPKWw^qG85M}*35?NA9C3T zLR)Hzj#ZdHEak!1<1f9o@1YN|n^z{$`>HnO zKGYVGY3r5c?>TyUmZB)m>01^wvve&6l5wAmx>ksPDSF0apLJ|Q0H!j&*08K}0Ow!B z!9y^udVMV{J97bdFcS!C8(Y9D8$&U=m@(>=HZ*A|iS4~@J7MGYU9jNPX)yoPsl1tb zc6JWrW@iU7vI*d{=!{t^6=TK>f#BfNv!A#Mx-oMG62g4k@aXOCz)}BRM2CvBOa8Zh zv2ukyE$tZ+Tgl-U1fAup>!K=H$y&XTTGDZyP}UngG#->pK*mLn!wKzZH0TmSWy6)2 zUMW95THnJ56>=bsA2kAUvog8CLIv=Tzq%Hj8fhZNc16HN4L*_mSTI9_MGZcBpj}SK zH7`AQ?=uMnAZ_2gW##&e?Cd3dTRUQsAtRf7E(^W11S3TS8gNH2ElbZoV!HL}`C4<# zV68HPJ4ZH`u{ynvoX%t!4j()KGBeVlF#>}r3dd>66vf)}-TmC-+@9F>MB3S{MUnKih zH&5je4(1m{UVoEuNegS9U9CE*fib-eBs|2jl8NotG0EvnPVB~x9$+24JoVmcVy#(E zW9@2jCW6nP2ZI58vdm0VuA%`Sou{|Ai$w30(adjD6t?CscQt|R{ed%v%nlJ`;T5ol z>UE-8zDmg<6Rb)xGnha~+03%fUI9t#_6S(YlFkw}mOHA}sw{Af{$+jribX8`9{45` zEo2Mui7uA}S-Z-y2?MAr|4^*;NT>)_wkvPG^NEbitm~yd57WsZ-jYppHls+^`V2j8 zkH4X|Ea-)*#UpE?C{}J3>p2XsQIL`KOk6FIf-j&sNETKyiwb-+UcEhCPrdxmeb+}j zlQAiA8QD2EJ9@efL7%&3f;8R{|6sZ}0~fG%C`9n{#Q?r|F(0|((CR%bF?MTAGa2hzag@G~6ucun3-v+PA9ciX z$hRmJKNurwEoCxHsD4!yRdQgxHd$m**_bse2V*{2wp1JRK0Ky(mJ8Nvz{zZ4;hNQn zXu-!=?94~^-P=uiAIvjLv+pwor0tnM-a)H9f8W{L(_gM>3pUi2eu|D>y*|jaH4=WE zP$fBk-GzarKy{mFUfO>WNSeP?{n3gk8PlI9U~_2xmLw*yv{~3rYj8+9H8ob|qmAZT zmc2iW6?}04AZ^e7>9@~$y`DRqJ^cwnwcb|Sa?SFIXU1qs05}A~6uTaLm?@O2n)^1nbeCI1^ z=^2;X`lOmpj_7^kZOR6zD2iB**9~5`TchI27RS<`mkIrl!}9T3So47+O6Izqd1+>1 zz(>wzhokr3FF$ne<#D5qc&`^sPU_UO^d<2Hp&YmU%|ve@P*^fvI1Yv2DEt3izi;<9?BpmwWFu|uqVQ$qvE@agLOh>SRz?w0hbH4>F})t z&IIXc^zxCltLSQhm!$5`%p@?=f36d|%J;4OnP9a169&HCo?^6kzY%xkDG@8CUwQML z1JF$0lAf74x&MmWBNNHGaZnB4fLP{CXjt~k*pr!Qj~~qJ52ZrgANfQD- zi7krO+1%#qipSDR(y>tOjy&lF^6PsIQ{OlZTrGIB%g0IgRP}#n9>)qePC+*R+ zjEro1N=lqrE&i6W@tRD;vBk3V(KBBp$B90fl*$rN-|NKd_(8nFYe^44zc`+VPu`pM z)U?S6L`a@Zj?$f@2VpoJMOo0!rr{hX`Wg-bAJ_L>&SIO*_LX=f79_n07Q}H$W^UdB z9DpafJKJLx$#Hxpg;@(PBE!Y8rg%w=aELESvW|*qRg6S{Pxg4ByQ`gyji<=c?Jw~4 zD+6k`t8Te-h1cW#XDW*1zOS|mhwUN|&;HI=v4~ykQiRw#7AP(jr^C^2A~z`ksJLom z*}H~G(vtLyEaX0KV9IGK&XxIe5)(CzDuGuwGIFP+>#-@dz^F*QkCr{t=YExo0g1 zSn)7<@NbSTE@TsB9~4z57D9$9)&h-h2XLY)vxz=ROI9lt~kr zdnxwjkdEGNN}Jz-66CH(dbOMcpeZB%_Q$eU{-tEM52FS9!UJOR+?NQ#M1hZh=XCT^ zbf%Wsq~D{seJI)I_JjbS84$@@);qD2`#yphCb5F=L*ovfxZFM+&nusBz&oJh0*Sq3lWh0*X9VLU0E4sQb$DM(hz$vhv{Y+fFk>V`=>wrRDAQ4F znLtNiMHOQMzE+tnd3q9n$;pZoS-A>t@ob#!3s7Io<}G>=jPx^}>=^A3YC*`26kdbu zZw=y=yAXBjQMKJj%iJfU5AP%ZBV)#k@OIC`sa}ltJl~f*VDpz<+I`D8L;%WIhRx@e z@d3`)ywb`4MxgD$Yuv&lcrTfKcP9ZDH$cK19D-Shrs-%^PNw&jzA%!%F}c>~NWX_O zT-wSx<#%E>-YwN%E= zkFn$t2+)XD0Wtv1|B6C@)!_fC{C{oc|7Y+%$k2?&!?u&7?c4refB^uImNL4h*Jefl O0000 + + + + + diff --git a/lollipin/src/main/res/drawable/ic_fingerprint_success.xml b/lollipin/src/main/res/drawable/ic_fingerprint_success.xml new file mode 100644 index 00000000..261f3e7f --- /dev/null +++ b/lollipin/src/main/res/drawable/ic_fingerprint_success.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/lollipin/src/main/res/drawable/pin_code_round_empty.xml b/lollipin/src/main/res/drawable/pin_code_round_empty.xml new file mode 100644 index 00000000..f6951e39 --- /dev/null +++ b/lollipin/src/main/res/drawable/pin_code_round_empty.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/lollipin/src/main/res/drawable/pin_code_round_full.xml b/lollipin/src/main/res/drawable/pin_code_round_full.xml new file mode 100644 index 00000000..614b0718 --- /dev/null +++ b/lollipin/src/main/res/drawable/pin_code_round_full.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/lollipin/src/main/res/layout/activity_pin_code.xml b/lollipin/src/main/res/layout/activity_pin_code.xml new file mode 100644 index 00000000..5e087a6a --- /dev/null +++ b/lollipin/src/main/res/layout/activity_pin_code.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lollipin/src/main/res/layout/view_keyboard.xml b/lollipin/src/main/res/layout/view_keyboard.xml new file mode 100644 index 00000000..79a54324 --- /dev/null +++ b/lollipin/src/main/res/layout/view_keyboard.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lollipin/src/main/res/layout/view_keyboard_button.xml b/lollipin/src/main/res/layout/view_keyboard_button.xml new file mode 100644 index 00000000..1dd03ebf --- /dev/null +++ b/lollipin/src/main/res/layout/view_keyboard_button.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/lollipin/src/main/res/layout/view_round.xml b/lollipin/src/main/res/layout/view_round.xml new file mode 100644 index 00000000..145056f2 --- /dev/null +++ b/lollipin/src/main/res/layout/view_round.xml @@ -0,0 +1,11 @@ + + diff --git a/lollipin/src/main/res/layout/view_round_pin_code.xml b/lollipin/src/main/res/layout/view_round_pin_code.xml new file mode 100644 index 00000000..87e84470 --- /dev/null +++ b/lollipin/src/main/res/layout/view_round_pin_code.xml @@ -0,0 +1,9 @@ + + + + diff --git a/lollipin/src/main/res/values-id/strings.xml b/lollipin/src/main/res/values-id/strings.xml new file mode 100644 index 00000000..9dd5b3c4 --- /dev/null +++ b/lollipin/src/main/res/values-id/strings.xml @@ -0,0 +1,14 @@ + + + Lupa? + Matikan %d-digit Kode pin + Buat %d-digit Kode pin + Masukan %d-digit Kode pin Anda + Masukan %d-digit Kode pin Anda + Pastikan %d-digit Kode pin Anda + + Tanda tangan + Tanda tangan dikenali + Tanda tangan tidak dikenali. Coba lagi + + diff --git a/lollipin/src/main/res/values-ko/strings.xml b/lollipin/src/main/res/values-ko/strings.xml new file mode 100644 index 00000000..d5195f48 --- /dev/null +++ b/lollipin/src/main/res/values-ko/strings.xml @@ -0,0 +1,14 @@ + + + 잊어버렸나요? + %d-자리 핀코드 해제 + %d-자리 핀코드 생성 + 입력한 %d-자리 핀코드를 입력하세요 + %d-자리 핀코드를 입력하세요 + %d-자리 핀코드를 한번 더 입력하세요 + + 지문인식 + 지문을 인식했습니다 + 지문을 인식하지 못 했습니다. 다시 시도해 주세요 + + \ No newline at end of file diff --git a/lollipin/src/main/res/values-pt/strings.xml b/lollipin/src/main/res/values-pt/strings.xml new file mode 100644 index 00000000..63aec50e --- /dev/null +++ b/lollipin/src/main/res/values-pt/strings.xml @@ -0,0 +1,12 @@ + + + Digital não reconhecida. Tente novamente + Digital reconhecida + Digital + Esqueceu? + Insira seu PIN atual de %d dígitos + Criar um PIN de %d dígitos + Desabilitar PIN de %d dígitos + Confirme seu PIN de %d dígitos + Insira seu PIN de %d dígitos + \ No newline at end of file diff --git a/lollipin/src/main/res/values-ru/strings.xml b/lollipin/src/main/res/values-ru/strings.xml new file mode 100644 index 00000000..f0699a69 --- /dev/null +++ b/lollipin/src/main/res/values-ru/strings.xml @@ -0,0 +1,12 @@ + + Забыли пароль? + Отключите %d-значный пин-код + Создайте %d-значный пин-код + Введите свой %d-значный пин-код + Введите свой %d-значный пин-код + Подтвердите ваш %d-значный пин-код + + Отпечаток пальца + Отпечаток принят + Отпечаток не принят. Попробуйте снова. + diff --git a/lollipin/src/main/res/values/attrs.xml b/lollipin/src/main/res/values/attrs.xml new file mode 100644 index 00000000..70b221a4 --- /dev/null +++ b/lollipin/src/main/res/values/attrs.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lollipin/src/main/res/values/colors.xml b/lollipin/src/main/res/values/colors.xml new file mode 100644 index 00000000..8e08b31e --- /dev/null +++ b/lollipin/src/main/res/values/colors.xml @@ -0,0 +1,13 @@ + + + #4a636f + #03a9f4 + #4b636f + #f0f6f9 + #9baab2 + #eaf2f5 + + #f4511e + #42000000 + #009688 + \ No newline at end of file diff --git a/lollipin/src/main/res/values/dimens.xml b/lollipin/src/main/res/values/dimens.xml new file mode 100644 index 00000000..5ec65fd1 --- /dev/null +++ b/lollipin/src/main/res/values/dimens.xml @@ -0,0 +1,21 @@ + + + 30sp + 15dp + 30dp + 25dp + 10dp + 20dp + 5dp + 50dp + 16sp + 20sp + 20dp + 20dp + 30dp + 10dp + + + + + \ No newline at end of file diff --git a/lollipin/src/main/res/values/integers.xml b/lollipin/src/main/res/values/integers.xml new file mode 100644 index 00000000..ed009de7 --- /dev/null +++ b/lollipin/src/main/res/values/integers.xml @@ -0,0 +1,4 @@ + + + 200 + \ No newline at end of file diff --git a/lollipin/src/main/res/values/strings.xml b/lollipin/src/main/res/values/strings.xml new file mode 100644 index 00000000..bd3e5bc0 --- /dev/null +++ b/lollipin/src/main/res/values/strings.xml @@ -0,0 +1,34 @@ + + lib + + 1 + 2 + ABC + 3 + DEF + 4 + GHI + 5 + JKL + 6 + MNO + 7 + PQRS + 8 + TUV + 9 + WXYZ + 0 + + Forgot? + Disable %d-digit Pincode + Create a %d-digit Pincode + Enter your current %d-digit Pincode + Enter your %d-digit Pincode + Confirm your %d-digit Pincode + + Fingerprint + Fingerprint recognized + Fingerprint not recognized. Try again + + From e313b838446e325b85d6e0944614205cb531c9e8 Mon Sep 17 00:00:00 2001 From: jayba Date: Tue, 3 Apr 2018 04:51:00 +0800 Subject: [PATCH 03/27] Caused by java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.hardware.fingerprint.FingerprintManager.isHardwareDetected()' on a null object reference at com.github.omadahealth.lollipin.lib.managers.AppLockActivity.initLayoutForFingerprint(AppLockActivity.java:1147) --- .../omadahealth/lollipin/lib/managers/AppLockActivity.java | 1 + .../omadahealth/lollipin/lib/managers/FingerprintUiHelper.java | 1 + 2 files changed, 2 insertions(+) diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java index a7b78457..40fc2598 100644 --- a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java @@ -145,6 +145,7 @@ private void initLayoutForFingerprint() { mFingerprintUiHelper = new FingerprintUiHelper.FingerprintUiHelperBuilder(mFingerprintManager).build(mFingerprintImageView, mFingerprintTextView, this); try { + //mFingerprintManager != null added by Jay if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected() && mFingerprintUiHelper.isFingerprintAuthAvailable() && mLockManager.getAppLock().isFingerprintAuthEnabled()) { mFingerprintImageView.setVisibility(View.VISIBLE); diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java index 5dc3e8e0..ab36e233 100644 --- a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java @@ -220,6 +220,7 @@ public void run() { * @throws SecurityException If the hardware is not available, or the permission are not set */ public boolean isFingerprintAuthAvailable() throws SecurityException { + //mFingerprintManager != null added by Jay return mFingerprintManager != null && mFingerprintManager.isHardwareDetected() && mFingerprintManager.hasEnrolledFingerprints() && ((KeyguardManager) mIcon.getContext().getSystemService(Context.KEYGUARD_SERVICE)).isDeviceSecure(); From 459b544af4419dd13d677f1a50790951fd86b5ab Mon Sep 17 00:00:00 2001 From: jayba Date: Tue, 3 Apr 2018 05:31:01 +0800 Subject: [PATCH 04/27] date picker will follow day of week settings --- lollipin/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lollipin/build.gradle b/lollipin/build.gradle index b5fc3918..21a3eaaa 100644 --- a/lollipin/build.gradle +++ b/lollipin/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' android { compileSdkVersion 27 - buildToolsVersion '26.0.2' + buildToolsVersion '27.0.3' defaultConfig { minSdkVersion 14 From 00f0d593de6547c6b00eceb6b4380e3efbb3e0e3 Mon Sep 17 00:00:00 2001 From: jay Date: Thu, 12 Apr 2018 09:40:49 +0800 Subject: [PATCH 05/27] updated libraries to v27.1.1 updated classpath 'com.android.tools.build:gradle:3.1.1' --- build.gradle | 2 +- lollipin/build.gradle | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 5cd0458e..fac8246e 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0' + classpath 'com.android.tools.build:gradle:3.1.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/lollipin/build.gradle b/lollipin/build.gradle index 21a3eaaa..c524e3c3 100644 --- a/lollipin/build.gradle +++ b/lollipin/build.gradle @@ -19,16 +19,16 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(dir: 'libs', include: ['*.jar']) //RippleView - compile 'com.github.traex.rippleeffect:ripple:1.3.1-OG' + implementation 'com.github.traex.rippleeffect:ripple:1.3.1-OG' //TypefaceView - compile 'com.github.omadahealth.typefaceview:typefaceview:1.5.0@aar' //TypefaceTextView + implementation 'com.github.omadahealth.typefaceview:typefaceview:1.5.0@aar' //TypefaceTextView //Compat - compile 'com.android.support:support-v4:27.1.0' - compile 'com.android.support:appcompat-v7:27.1.0' - compile "com.android.support:support-v13:27.1.0" + implementation 'com.android.support:support-v4:27.1.1' + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation "com.android.support:support-v13:27.1.1" } repositories { maven { From 61dcfc89349003ca292df0f81e6ce5c567820b6a Mon Sep 17 00:00:00 2001 From: jay Date: Thu, 12 Apr 2018 10:17:39 +0800 Subject: [PATCH 06/27] fix for Fatal Exception: java.lang.RuntimeException: Unable to resume activity {com.rammigsoftware.bluecoins/com.rammigsoftware.bluecoins.pinsecurity.CustomPinActivity}: java.lang.RuntimeException: java.security.InvalidAlgorithmParameterException: java.lang.IllegalStateException: At least one fingerprint must be enrolled to create keys requiring user authentication for every use at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3493) --- .../lollipin/lib/managers/FingerprintUiHelper.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java index ab36e233..6c5e7ccf 100644 --- a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java @@ -274,7 +274,16 @@ public void createKey() { .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .build()); mKeyGenerator.generateKey(); - } catch (NoSuchProviderException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { + + } catch (InvalidAlgorithmParameterException e){ + // added by Jay + // Fatal Exception: java.lang.RuntimeException: Unable to resume activity {com.rammigsoftware.bluecoins/com.rammigsoftware.bluecoins + // .pinsecurity.CustomPinActivity}: java.lang.RuntimeException: java.security.InvalidAlgorithmParameterException: java.lang + // .IllegalStateException: At least one fingerprint must be enrolled to create keys requiring user authentication for every use + // at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3493) + e.printStackTrace(); + + } catch (NoSuchProviderException | NoSuchAlgorithmException e) { throw new RuntimeException(e); } } From df60c991ae3901302e8d73a35b65279f4066c0c9 Mon Sep 17 00:00:00 2001 From: jay Date: Thu, 12 Apr 2018 10:18:00 +0800 Subject: [PATCH 07/27] fix for Fatal Exception: java.lang.RuntimeException: Unable to resume activity {com.rammigsoftware.bluecoins/com.rammigsoftware.bluecoins.pinsecurity.CustomPinActivity}: java.lang.RuntimeException: java.security.InvalidAlgorithmParameterException: java.lang.IllegalStateException: At least one fingerprint must be enrolled to create keys requiring user authentication for every use at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3493) --- .../omadahealth/lollipin/lib/managers/FingerprintUiHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java index 6c5e7ccf..fdbe7a7c 100644 --- a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java @@ -282,7 +282,7 @@ public void createKey() { // .IllegalStateException: At least one fingerprint must be enrolled to create keys requiring user authentication for every use // at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3493) e.printStackTrace(); - + } catch (NoSuchProviderException | NoSuchAlgorithmException e) { throw new RuntimeException(e); } From da2c7ef6d54062944f3c20797910aa37b22ad27a Mon Sep 17 00:00:00 2001 From: jay Date: Thu, 12 Apr 2018 10:19:41 +0800 Subject: [PATCH 08/27] fix for Fatal Exception: java.lang.RuntimeException: Unable to resume activity {com.rammigsoftware.bluecoins/com.rammigsoftware.bluecoins.pinsecurity.CustomPinActivity}: java.lang.RuntimeException: java.security.InvalidAlgorithmParameterException: java.lang.IllegalStateException: At least one fingerprint must be enrolled to create keys requiring user authentication for every use at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3493) --- .../lollipin/lib/managers/FingerprintUiHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java index fdbe7a7c..4cff054f 100644 --- a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/FingerprintUiHelper.java @@ -276,11 +276,11 @@ public void createKey() { mKeyGenerator.generateKey(); } catch (InvalidAlgorithmParameterException e){ - // added by Jay + // added by Jay. Appears to be an issue specific to Sony Experia Z5 // Fatal Exception: java.lang.RuntimeException: Unable to resume activity {com.rammigsoftware.bluecoins/com.rammigsoftware.bluecoins // .pinsecurity.CustomPinActivity}: java.lang.RuntimeException: java.security.InvalidAlgorithmParameterException: java.lang // .IllegalStateException: At least one fingerprint must be enrolled to create keys requiring user authentication for every use - // at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3493) + // at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3493) e.printStackTrace(); } catch (NoSuchProviderException | NoSuchAlgorithmException e) { From 7ecec46aa34cb387f61c068cd06c9a48c1b49c5d Mon Sep 17 00:00:00 2001 From: jayba Date: Wed, 9 May 2018 08:57:15 +0800 Subject: [PATCH 09/27] gradle update --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index fac8246e..3a8b3143 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ buildscript { repositories { jcenter() + google() } dependencies { classpath 'com.android.tools.build:gradle:3.1.1' From 877611b825f86762502cda1cd502f16d8a888453 Mon Sep 17 00:00:00 2001 From: jayba Date: Wed, 30 May 2018 10:52:56 +0800 Subject: [PATCH 10/27] android.security.KeyStore.getKeyStoreException (Unknown Source:29) javax.crypto.KeyGenerator.generateKey (KeyGenerator.java:604) com.github.omadahealth.lollipin.lib.managers.FingerprintUiHelper.createKey (FingerprintUiHelper.java:1276) --- .../omadahealth/lollipin/lib/managers/AppLockActivity.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java index 40fc2598..05f87ef5 100644 --- a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java @@ -20,6 +20,7 @@ import com.github.omadahealth.lollipin.lib.views.KeyboardView; import com.github.omadahealth.lollipin.lib.views.PinCodeRoundView; +import java.security.ProviderException; import java.util.Arrays; import java.util.List; @@ -155,8 +156,8 @@ private void initLayoutForFingerprint() { mFingerprintImageView.setVisibility(View.GONE); mFingerprintTextView.setVisibility(View.GONE); } - } catch (SecurityException e) { - Log.e(TAG, e.toString()); + //} catch (SecurityException e) { + } catch (SecurityException | ProviderException e) {//added ProviderException to resolve field issues mFingerprintImageView.setVisibility(View.GONE); mFingerprintTextView.setVisibility(View.GONE); } From 59e90a74f762385b7052ae1df0e0b41ffbd50b2d Mon Sep 17 00:00:00 2001 From: jay Date: Wed, 20 Jun 2018 08:49:42 +0800 Subject: [PATCH 11/27] gradle update --- build.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index fac8246e..a5aa3ebb 100644 --- a/build.gradle +++ b/build.gradle @@ -2,10 +2,11 @@ buildscript { repositories { + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.1' + classpath 'com.android.tools.build:gradle:3.1.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -14,7 +15,8 @@ buildscript { allprojects { repositories { - maven{ + google() + maven { url "https://github.com/omadahealth/omada-nexus/raw/master/release" } jcenter() From 44bf57ef75ac1dad952a3fbb856f0acf600cfc8e Mon Sep 17 00:00:00 2001 From: jay Date: Wed, 20 Jun 2018 08:52:13 +0800 Subject: [PATCH 12/27] gradle update --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9fdfb90c..a5aa3ebb 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,6 @@ buildscript { repositories { google() jcenter() - google() } dependencies { classpath 'com.android.tools.build:gradle:3.1.2' From 45abeb17d5d00cb4de9f4862b4dbddffe16bf705 Mon Sep 17 00:00:00 2001 From: jay Date: Mon, 27 Aug 2018 23:02:02 +0800 Subject: [PATCH 13/27] updated --- lollipin/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lollipin/build.gradle b/lollipin/build.gradle index c524e3c3..d73e0029 100644 --- a/lollipin/build.gradle +++ b/lollipin/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' android { compileSdkVersion 27 - buildToolsVersion '27.0.3' + buildToolsVersion '28.0.2' defaultConfig { minSdkVersion 14 From 1f24c64d24e567362d249660583c671426576038 Mon Sep 17 00:00:00 2001 From: jayba Date: Tue, 28 Aug 2018 11:04:42 +0800 Subject: [PATCH 14/27] update --- lollipin/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lollipin/build.gradle b/lollipin/build.gradle index c524e3c3..d73e0029 100644 --- a/lollipin/build.gradle +++ b/lollipin/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' android { compileSdkVersion 27 - buildToolsVersion '27.0.3' + buildToolsVersion '28.0.2' defaultConfig { minSdkVersion 14 From cd8a19404ace877dad4041f5f512812701bd4f18 Mon Sep 17 00:00:00 2001 From: jay Date: Sat, 1 Sep 2018 15:58:16 +0800 Subject: [PATCH 15/27] gradle update --- lollipin/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lollipin/build.gradle b/lollipin/build.gradle index d73e0029..504dfe30 100644 --- a/lollipin/build.gradle +++ b/lollipin/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 27 + compileSdkVersion 28 buildToolsVersion '28.0.2' defaultConfig { minSdkVersion 14 - targetSdkVersion 27 + targetSdkVersion 28 versionCode 2 versionName VERSION_NAME } From 62632fc1ed25981aa602c5a648aab4e05aa6de12 Mon Sep 17 00:00:00 2001 From: jay Date: Sun, 23 Sep 2018 22:41:12 +0800 Subject: [PATCH 16/27] updated support libraries to V28 --- lollipin/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lollipin/build.gradle b/lollipin/build.gradle index 504dfe30..50ff73be 100644 --- a/lollipin/build.gradle +++ b/lollipin/build.gradle @@ -26,9 +26,9 @@ dependencies { implementation 'com.github.omadahealth.typefaceview:typefaceview:1.5.0@aar' //TypefaceTextView //Compat - implementation 'com.android.support:support-v4:27.1.1' - implementation 'com.android.support:appcompat-v7:27.1.1' - implementation "com.android.support:support-v13:27.1.1" + implementation 'com.android.support:support-v4:28.0.0' + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation "com.android.support:support-v13:28.0.0" } repositories { maven { From 566bfaa1281bb01d5c5e6f3e97ee4d2398a4e4c0 Mon Sep 17 00:00:00 2001 From: jay Date: Mon, 24 Sep 2018 08:15:39 +0800 Subject: [PATCH 17/27] revert support libraries to V27 --- build.gradle | 2 +- lollipin/build.gradle | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index a5aa3ebb..9ade757b 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.2' + classpath 'com.android.tools.build:gradle:3.1.4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/lollipin/build.gradle b/lollipin/build.gradle index 50ff73be..d73e0029 100644 --- a/lollipin/build.gradle +++ b/lollipin/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 28 + compileSdkVersion 27 buildToolsVersion '28.0.2' defaultConfig { minSdkVersion 14 - targetSdkVersion 28 + targetSdkVersion 27 versionCode 2 versionName VERSION_NAME } @@ -26,9 +26,9 @@ dependencies { implementation 'com.github.omadahealth.typefaceview:typefaceview:1.5.0@aar' //TypefaceTextView //Compat - implementation 'com.android.support:support-v4:28.0.0' - implementation 'com.android.support:appcompat-v7:28.0.0' - implementation "com.android.support:support-v13:28.0.0" + implementation 'com.android.support:support-v4:27.1.1' + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation "com.android.support:support-v13:27.1.1" } repositories { maven { From 488c21fb1857fafbc7c75e8540166b994322813f Mon Sep 17 00:00:00 2001 From: jay Date: Fri, 12 Oct 2018 07:45:12 +0800 Subject: [PATCH 18/27] updated buildToolsVersion --- lollipin/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lollipin/build.gradle b/lollipin/build.gradle index d73e0029..80008146 100644 --- a/lollipin/build.gradle +++ b/lollipin/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' android { compileSdkVersion 27 - buildToolsVersion '28.0.2' + buildToolsVersion '28.0.3' defaultConfig { minSdkVersion 14 From f8a37aad673d2de6a34d074be4cc344c16033214 Mon Sep 17 00:00:00 2001 From: jay Date: Sat, 13 Oct 2018 10:20:54 +0800 Subject: [PATCH 19/27] changed compile to implementation --- app/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 82946b17..e3a28c2f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,10 +20,10 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(dir: 'libs', include: ['*.jar']) - compile project(':lib') - compile 'com.android.support:appcompat-v7:26.0.2' + implementation project(':lib') + implementation 'com.android.support:appcompat-v7:26.0.2' //Lollipop dialogs https://github.com/lewisjdeane/L-Dialogs and buttons, animations etc... compile 'uk.me.lewisdeane.ldialogs:ldialogs:1.2.0@aar' From 7acb5447cd2d097b9a13eb8707a872258ef8d344 Mon Sep 17 00:00:00 2001 From: jay Date: Sat, 13 Oct 2018 10:23:02 +0800 Subject: [PATCH 20/27] updated classpath --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9ade757b..7618fa82 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.4' + classpath 'com.android.tools.build:gradle:3.2.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 78ea407a10ecfb09a191833798acc5e9346b39b9 Mon Sep 17 00:00:00 2001 From: jayba Date: Thu, 25 Oct 2018 18:55:10 +0800 Subject: [PATCH 21/27] NPE --- .../omadahealth/lollipin/lib/managers/AppLockActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java index 05f87ef5..b5256424 100644 --- a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java @@ -148,7 +148,7 @@ private void initLayoutForFingerprint() { try { //mFingerprintManager != null added by Jay if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected() && mFingerprintUiHelper.isFingerprintAuthAvailable() - && mLockManager.getAppLock().isFingerprintAuthEnabled()) { + && mLockManager.getAppLock() != null && mLockManager.getAppLock().isFingerprintAuthEnabled()) { mFingerprintImageView.setVisibility(View.VISIBLE); mFingerprintTextView.setVisibility(View.VISIBLE); mFingerprintUiHelper.startListening(); From 73e6707fdfebc897ffd81ff4959aaf5d5b4343d7 Mon Sep 17 00:00:00 2001 From: jay Date: Thu, 6 Dec 2018 16:51:45 +0800 Subject: [PATCH 22/27] auto-refactor --- .../github/omadahealth/lollipin/LockedCompatActivity.java | 2 +- lollipin/build.gradle | 6 +++--- .../com/github/omadahealth/lollipin/lib/PinActivity.java | 2 +- .../github/omadahealth/lollipin/lib/PinCompatActivity.java | 4 ++-- .../omadahealth/lollipin/lib/PinFragmentActivity.java | 4 ++-- .../omadahealth/lollipin/lib/managers/AppLockActivity.java | 2 +- .../omadahealth/lollipin/lib/views/SquareImageView.java | 3 +-- 7 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/github/omadahealth/lollipin/LockedCompatActivity.java b/app/src/main/java/com/github/omadahealth/lollipin/LockedCompatActivity.java index 93141c29..019950a8 100644 --- a/app/src/main/java/com/github/omadahealth/lollipin/LockedCompatActivity.java +++ b/app/src/main/java/com/github/omadahealth/lollipin/LockedCompatActivity.java @@ -1,7 +1,7 @@ package com.github.omadahealth.lollipin; import android.os.Bundle; -import android.support.v7.widget.Toolbar; +import androidx.appcompat.widget.Toolbar; import com.github.omadahealth.lollipin.lib.PinCompatActivity; import lollipin.orangegangsters.github.com.lollipin.R; diff --git a/lollipin/build.gradle b/lollipin/build.gradle index 80008146..a3fddd6a 100644 --- a/lollipin/build.gradle +++ b/lollipin/build.gradle @@ -26,9 +26,9 @@ dependencies { implementation 'com.github.omadahealth.typefaceview:typefaceview:1.5.0@aar' //TypefaceTextView //Compat - implementation 'com.android.support:support-v4:27.1.1' - implementation 'com.android.support:appcompat-v7:27.1.1' - implementation "com.android.support:support-v13:27.1.1" + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation "androidx.legacy:legacy-support-v13:1.0.0" } repositories { maven { diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinActivity.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinActivity.java index 11ed7d6c..0434386d 100644 --- a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinActivity.java +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinActivity.java @@ -7,7 +7,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface; import com.github.omadahealth.lollipin.lib.managers.AppLockActivity; diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinCompatActivity.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinCompatActivity.java index 15c06d51..bbc5b864 100644 --- a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinCompatActivity.java +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinCompatActivity.java @@ -5,8 +5,8 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; -import android.support.v7.app.AppCompatActivity; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.appcompat.app.AppCompatActivity; import com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface; import com.github.omadahealth.lollipin.lib.managers.AppLockActivity; diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinFragmentActivity.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinFragmentActivity.java index 78c49479..e2c08717 100644 --- a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinFragmentActivity.java +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/PinFragmentActivity.java @@ -5,8 +5,8 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; -import android.support.v4.app.FragmentActivity; -import android.support.v4.content.LocalBroadcastManager; +import androidx.fragment.app.FragmentActivity; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.github.omadahealth.lollipin.lib.interfaces.LifeCycleInterface; import com.github.omadahealth.lollipin.lib.managers.AppLockActivity; diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java index b5256424..76561725 100644 --- a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java @@ -5,7 +5,7 @@ import android.hardware.fingerprint.FingerprintManager; import android.os.Build; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.util.Log; import android.view.View; import android.view.animation.Animation; diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/SquareImageView.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/SquareImageView.java index 578a0b00..e73e80ff 100644 --- a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/SquareImageView.java +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/views/SquareImageView.java @@ -2,12 +2,11 @@ import android.content.Context; import android.util.AttributeSet; -import android.widget.ImageView; /** * An ImageView that shrinks its larger dimension to become square. */ -public class SquareImageView extends android.support.v7.widget.AppCompatImageView { +public class SquareImageView extends androidx.appcompat.widget.AppCompatImageView { public SquareImageView(Context context) { super(context); } From 89fa930252ab21b78352515d78362ac4ee9fcc4f Mon Sep 17 00:00:00 2001 From: jay Date: Fri, 7 Dec 2018 09:50:41 +0800 Subject: [PATCH 23/27] RebindReportingHolder auto-refactoring issue --- lollipin/build.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lollipin/build.gradle b/lollipin/build.gradle index a3fddd6a..867c9d25 100644 --- a/lollipin/build.gradle +++ b/lollipin/build.gradle @@ -25,10 +25,12 @@ dependencies { //TypefaceView implementation 'com.github.omadahealth.typefaceview:typefaceview:1.5.0@aar' //TypefaceTextView + //implementation 'androidx.fragment:fragment:1.0.0' + //Compat - implementation 'androidx.legacy:legacy-support-v4:1.0.0' + //implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.appcompat:appcompat:1.0.2' - implementation "androidx.legacy:legacy-support-v13:1.0.0" + //implementation "androidx.legacy:legacy-support-v13:1.0.0" } repositories { maven { From 78c10ee52a0464199e029eea3281d1d56457e3ac Mon Sep 17 00:00:00 2001 From: jay Date: Mon, 10 Dec 2018 08:02:36 +0800 Subject: [PATCH 24/27] update target SDK to 28 --- app/build.gradle | 8 ++++---- lollipin/build.gradle | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e3a28c2f..3d2a96bb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,12 +2,12 @@ apply plugin: 'com.android.application' android { compileSdkVersion 26 - buildToolsVersion '26.0.2' + buildToolsVersion '26.0.3' defaultConfig { applicationId "com.github.orangegangsters.lollipin" minSdkVersion 14 - targetSdkVersion 24 + targetSdkVersion 28 versionCode 1 versionName "1.0" } @@ -23,10 +23,10 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':lib') - implementation 'com.android.support:appcompat-v7:26.0.2' + implementation 'com.android.support:appcompat-v7:26.1.0' //Lollipop dialogs https://github.com/lewisjdeane/L-Dialogs and buttons, animations etc... - compile 'uk.me.lewisdeane.ldialogs:ldialogs:1.2.0@aar' + implementation 'uk.me.lewisdeane.ldialogs:ldialogs:1.2.0@aar' //test androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.5.2' diff --git a/lollipin/build.gradle b/lollipin/build.gradle index 867c9d25..277aafc1 100644 --- a/lollipin/build.gradle +++ b/lollipin/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 27 + compileSdkVersion 28 buildToolsVersion '28.0.3' defaultConfig { minSdkVersion 14 - targetSdkVersion 27 + targetSdkVersion 28 versionCode 2 versionName VERSION_NAME } From bc9e4aee1d6ba55bf2cefe66dd4d6eef47f996e4 Mon Sep 17 00:00:00 2001 From: jay Date: Mon, 10 Dec 2018 08:06:13 +0800 Subject: [PATCH 25/27] updated compile to implementation --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3d2a96bb..03e7dabf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,7 +29,7 @@ dependencies { implementation 'uk.me.lewisdeane.ldialogs:ldialogs:1.2.0@aar' //test - androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.5.2' + androidTestImplementation 'com.jayway.android.robotium:robotium-solo:5.5.2' } // REQUIRED: Google's new Maven repo is required for the latest From 79953f296dd1735559b46b5053b3658336fd3157 Mon Sep 17 00:00:00 2001 From: jayba Date: Fri, 15 Feb 2019 15:52:43 +0800 Subject: [PATCH 26/27] speed improvements --- lollipin/src/main/res/anim/nothing.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lollipin/src/main/res/anim/nothing.xml b/lollipin/src/main/res/anim/nothing.xml index 77bb8151..95d5ce9d 100644 --- a/lollipin/src/main/res/anim/nothing.xml +++ b/lollipin/src/main/res/anim/nothing.xml @@ -1,5 +1,5 @@ \ No newline at end of file From 22d4ab672d95bdd9f3a72a0794c1dda65d94b017 Mon Sep 17 00:00:00 2001 From: jay Date: Tue, 5 Mar 2019 21:52:02 +0800 Subject: [PATCH 27/27] optimize BasePresenter --- .../lib/managers/AppLockActivity.java | 30 ++++++++----------- .../src/main/res/layout/activity_pin_code.xml | 15 +++++----- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java index 76561725..cfd7edf0 100644 --- a/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java +++ b/lollipin/src/main/java/com/github/omadahealth/lollipin/lib/managers/AppLockActivity.java @@ -63,7 +63,6 @@ public abstract class AppLockActivity extends PinActivity implements KeyboardBut @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(getContentView()); initLayout(getIntent()); } @@ -74,7 +73,6 @@ protected void onCreate(Bundle savedInstanceState) { @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - initLayout(intent); } @@ -97,10 +95,8 @@ protected void onPause() { * Init completely the layout, depending of the extra {@link com.github.omadahealth.lollipin.lib.managers.AppLock#EXTRA_TYPE} */ private void initLayout(Intent intent) { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) { - //Animate if greater than 2.3.3 - overridePendingTransition(R.anim.nothing, R.anim.nothing); - } + //Animate if greater than 2.3.3 + overridePendingTransition(R.anim.nothing, R.anim.nothing); Bundle extras = intent.getExtras(); if (extras != null) { @@ -114,16 +110,16 @@ private void initLayout(Intent intent) { enableAppLockerIfDoesNotExist(); mLockManager.getAppLock().setPinChallengeCancelled(false); - mStepTextView = (TextView) this.findViewById(R.id.pin_code_step_textview); - mPinCodeRoundView = (PinCodeRoundView) this.findViewById(R.id.pin_code_round_view); - mPinCodeRoundView.setPinLength(this.getPinLength()); - mForgotTextView = (TextView) this.findViewById(R.id.pin_code_forgot_textview); + mStepTextView = findViewById(R.id.pin_code_step_textview); + mPinCodeRoundView = findViewById(R.id.pin_code_round_view); + mPinCodeRoundView.setPinLength(getPinLength()); + mForgotTextView = findViewById(R.id.pin_code_forgot_textview); mForgotTextView.setOnClickListener(this); - mKeyboardView = (KeyboardView) this.findViewById(R.id.pin_code_keyboard_view); + mKeyboardView = findViewById(R.id.pin_code_keyboard_view); mKeyboardView.setKeyboardButtonClickedListener(this); int logoId = mLockManager.getAppLock().getLogoId(); - ImageView logoImage = ((ImageView) findViewById(R.id.pin_code_logo_imageview)); + ImageView logoImage = findViewById(R.id.pin_code_logo_imageview); if (logoId != AppLock.LOGO_ID_NONE) { logoImage.setVisibility(View.VISIBLE); logoImage.setImageResource(logoId); @@ -139,8 +135,8 @@ private void initLayout(Intent intent) { * and {@link FingerprintManager#isHardwareDetected()}. */ private void initLayoutForFingerprint() { - mFingerprintImageView = (ImageView) this.findViewById(R.id.pin_code_fingerprint_imageview); - mFingerprintTextView = (TextView) this.findViewById(R.id.pin_code_fingerprint_textview); + mFingerprintImageView = findViewById(R.id.pin_code_fingerprint_imageview); + mFingerprintTextView = findViewById(R.id.pin_code_fingerprint_textview); if (mType == AppLock.UNLOCK_PIN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { mFingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE); mFingerprintUiHelper = new FingerprintUiHelper.FingerprintUiHelperBuilder(mFingerprintManager).build(mFingerprintImageView, @@ -242,10 +238,8 @@ public void finish() { } } - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) { - //Animate if greater than 2.3.3 - overridePendingTransition(R.anim.nothing, R.anim.slide_down); - } + //Animate if greater than 2.3.3 + overridePendingTransition(R.anim.nothing, R.anim.slide_down); } /** diff --git a/lollipin/src/main/res/layout/activity_pin_code.xml b/lollipin/src/main/res/layout/activity_pin_code.xml index 5e087a6a..2847b170 100644 --- a/lollipin/src/main/res/layout/activity_pin_code.xml +++ b/lollipin/src/main/res/layout/activity_pin_code.xml @@ -1,6 +1,5 @@ + android:paddingTop="@dimen/activity_pin_code_padding" + android:paddingBottom="@dimen/activity_pin_code_padding"> + android:layout_marginTop="@dimen/pin_code_round_top_margin" + android:layout_marginBottom="@dimen/pin_code_elements_margin" /> @@ -86,8 +85,8 @@ android:id="@+id/pin_code_fingerprint_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_centerInParent="true" android:layout_alignParentBottom="true" + android:layout_centerInParent="true" android:maxLines="1" android:textColor="@color/dark_grey_color" android:textSize="@dimen/pin_code_forgot_text_size"