Pin
22
LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 OrangeGangsters
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
1
app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
43
app/build.gradle
Normal file
@ -0,0 +1,43 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
buildToolsVersion '26.0.2'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.github.orangegangsters.lollipin"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 24
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
compile project(':lib')
|
||||
compile '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'
|
||||
|
||||
//test
|
||||
androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.5.2'
|
||||
}
|
||||
|
||||
// REQUIRED: Google's new Maven repo is required for the latest
|
||||
// support library that is compatible with Android 8.0
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://maven.google.com'
|
||||
// Alternative URL is 'https://dl.google.com/dl/android/maven2/'
|
||||
}
|
||||
}
|
||||
|
17
app/proguard-rules.pro
vendored
Normal file
@ -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 *;
|
||||
#}
|
@ -0,0 +1,69 @@
|
||||
package lollipin.orangegangsters.github.com.lollipin.functional;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
import android.view.View;
|
||||
|
||||
import com.github.omadahealth.lollipin.MainActivity;
|
||||
import com.github.omadahealth.lollipin.lib.managers.LockManager;
|
||||
import com.robotium.solo.Solo;
|
||||
|
||||
/**
|
||||
* Created by stoyan and oliviergoutay on 1/13/15.
|
||||
*/
|
||||
public class AbstractTest extends ActivityInstrumentationTestCase2<MainActivity> {
|
||||
|
||||
protected static final String PASSWORD_PREFERENCE_KEY = "PASSCODE";
|
||||
protected static final String PASSWORD_ALGORITHM_PREFERENCE_KEY = "ALGORITHM";
|
||||
private static final String LAST_ACTIVE_MILLIS_PREFERENCE_KEY = "LAST_ACTIVE_MILLIS";
|
||||
protected static final String ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY = "ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY";
|
||||
|
||||
protected Solo solo;
|
||||
|
||||
public AbstractTest() {
|
||||
super(MainActivity.class);
|
||||
}
|
||||
|
||||
public void setUp() throws Exception {
|
||||
solo = new Solo(getInstrumentation(), getActivity());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
removeAllPrefs();
|
||||
solo.finishOpenedActivities();
|
||||
}
|
||||
|
||||
/**
|
||||
* Click on the specified view id
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
protected void clickOnView(int id) {
|
||||
solo.sleep(300);
|
||||
solo.clickOnView(getView(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view for the specified id
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
protected View getView(int id) {
|
||||
return solo.getView(id);
|
||||
}
|
||||
|
||||
protected void removeAllPrefs() {
|
||||
LockManager.getInstance().getAppLock().disableAndRemoveConfiguration();
|
||||
}
|
||||
|
||||
protected void setMillis(long millis) {
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
SharedPreferences.Editor editor = sharedPref.edit();
|
||||
editor.putLong(LAST_ACTIVE_MILLIS_PREFERENCE_KEY, millis);
|
||||
editor.apply();
|
||||
}
|
||||
}
|
@ -0,0 +1,339 @@
|
||||
package lollipin.orangegangsters.github.com.lollipin.functional;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.github.omadahealth.lollipin.CustomPinActivity;
|
||||
import com.github.omadahealth.lollipin.MainActivity;
|
||||
import com.github.omadahealth.lollipin.NotLockedActivity;
|
||||
import com.github.omadahealth.lollipin.lib.encryption.Encryptor;
|
||||
import com.github.omadahealth.lollipin.lib.enums.Algorithm;
|
||||
import com.github.omadahealth.lollipin.lib.managers.AppLockImpl;
|
||||
import com.github.omadahealth.lollipin.lib.managers.FingerprintUiHelper;
|
||||
import com.github.omadahealth.lollipin.lib.managers.LockManager;
|
||||
import com.github.omadahealth.lollipin.lib.views.PinCodeRoundView;
|
||||
|
||||
import lollipin.orangegangsters.github.com.lollipin.R;
|
||||
|
||||
/**
|
||||
* @author stoyan and oliviergoutay
|
||||
* @version 1/13/15
|
||||
*/
|
||||
public class PinLockTest extends AbstractTest {
|
||||
|
||||
public void testMigratingFromSha1toSha256() {
|
||||
//Init
|
||||
removeAllPrefs();
|
||||
AppLockImpl appLockImpl = (AppLockImpl) LockManager.getInstance().getAppLock();
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
|
||||
//Should use sha256 if the SharedPreferences is set, by default
|
||||
enablePin();
|
||||
assertEquals(Algorithm.SHA256, Algorithm.getFromText(sharedPref.getString(PASSWORD_ALGORITHM_PREFERENCE_KEY, "")));
|
||||
assertTrue(appLockImpl.checkPasscode("1234"));
|
||||
removeAllPrefs();
|
||||
|
||||
//Should still use sha1 if password is stored but not the algorithm
|
||||
SharedPreferences.Editor editor = sharedPref.edit();
|
||||
editor.putString(PASSWORD_PREFERENCE_KEY, Encryptor.getSHA(appLockImpl.getSalt() + "test" + appLockImpl.getSalt(), Algorithm.SHA1));
|
||||
editor.apply();
|
||||
assertEquals(Algorithm.SHA1, Algorithm.getFromText(sharedPref.getString(PASSWORD_ALGORITHM_PREFERENCE_KEY, "")));
|
||||
assertTrue(appLockImpl.checkPasscode("test"));
|
||||
removeAllPrefs();
|
||||
}
|
||||
|
||||
public void testPinClearButton() {
|
||||
removePrefsAndGoToEnable();
|
||||
|
||||
//Enter 3 codes
|
||||
clickOnView(R.id.pin_code_button_1);
|
||||
clickOnView(R.id.pin_code_button_2);
|
||||
clickOnView(R.id.pin_code_button_3);
|
||||
|
||||
//Check length 3
|
||||
solo.sleep(1000);
|
||||
PinCodeRoundView pinCodeRoundView = (PinCodeRoundView) solo.getCurrentActivity().findViewById(com.github.omadahealth.lollipin.lib.R.id.pin_code_round_view);
|
||||
assertEquals(3, pinCodeRoundView.getCurrentLength());
|
||||
|
||||
//Click clear button
|
||||
clickOnView(R.id.pin_code_button_clear);
|
||||
|
||||
//Check length 0
|
||||
solo.sleep(1000);
|
||||
assertEquals(2, pinCodeRoundView.getCurrentLength());
|
||||
}
|
||||
|
||||
public void testPinEnabling() {
|
||||
removePrefsAndGoToEnable();
|
||||
|
||||
//Test no fingerprint
|
||||
assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_imageview).getVisibility());
|
||||
assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_textview).getVisibility());
|
||||
|
||||
//--------Not the same pin--------
|
||||
//Enter 4 codes
|
||||
clickOnView(R.id.pin_code_button_1);
|
||||
clickOnView(R.id.pin_code_button_2);
|
||||
clickOnView(R.id.pin_code_button_3);
|
||||
clickOnView(R.id.pin_code_button_4);
|
||||
solo.sleep(1000);
|
||||
clickOnView(R.id.pin_code_button_2);
|
||||
clickOnView(R.id.pin_code_button_3);
|
||||
clickOnView(R.id.pin_code_button_4);
|
||||
clickOnView(R.id.pin_code_button_5);
|
||||
solo.waitForActivity(CustomPinActivity.class);
|
||||
solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class);
|
||||
solo.sleep(1000);
|
||||
|
||||
//--------Same pin--------
|
||||
enablePin();
|
||||
}
|
||||
|
||||
public void testPinEnablingChecking() throws SecurityException {
|
||||
enablePin();
|
||||
|
||||
//Go to unlock
|
||||
clickOnView(R.id.button_unlock_pin);
|
||||
solo.waitForActivity(CustomPinActivity.class);
|
||||
solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class);
|
||||
|
||||
//Test fingerprint if available
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
ImageView fingerprintImageView = (ImageView) solo.getView(com.github.omadahealth.lollipin.lib.R.id.pin_code_fingerprint_imageview);
|
||||
TextView fingerprintTextView = (TextView) solo.getView(com.github.omadahealth.lollipin.lib.R.id.pin_code_fingerprint_textview);
|
||||
FingerprintManager fingerprintManager = (FingerprintManager) getActivity().getSystemService(Context.FINGERPRINT_SERVICE);
|
||||
FingerprintUiHelper fingerprintUiHelper = new FingerprintUiHelper.FingerprintUiHelperBuilder(fingerprintManager).build(fingerprintImageView, fingerprintTextView, (CustomPinActivity) solo.getCurrentActivity());
|
||||
if (fingerprintManager.isHardwareDetected() && fingerprintUiHelper.isFingerprintAuthAvailable()) {
|
||||
assertEquals(View.VISIBLE, solo.getView(R.id.pin_code_fingerprint_imageview).getVisibility());
|
||||
assertEquals(View.VISIBLE, solo.getView(R.id.pin_code_fingerprint_textview).getVisibility());
|
||||
} else {
|
||||
assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_imageview).getVisibility());
|
||||
assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_textview).getVisibility());
|
||||
}
|
||||
} else {
|
||||
assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_imageview).getVisibility());
|
||||
assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_textview).getVisibility());
|
||||
}
|
||||
|
||||
//Enter the code
|
||||
clickOnView(R.id.pin_code_button_1);
|
||||
clickOnView(R.id.pin_code_button_2);
|
||||
clickOnView(R.id.pin_code_button_3);
|
||||
clickOnView(R.id.pin_code_button_4);
|
||||
|
||||
//Check view
|
||||
solo.waitForActivity(MainActivity.class);
|
||||
solo.assertCurrentActivity("MainActivity", MainActivity.class);
|
||||
}
|
||||
|
||||
public void testPinEnablingChanging() {
|
||||
enablePin();
|
||||
|
||||
//Go to change
|
||||
clickOnView(R.id.button_change_pin);
|
||||
solo.waitForActivity(CustomPinActivity.class);
|
||||
solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class);
|
||||
|
||||
//Enter previous code
|
||||
clickOnView(R.id.pin_code_button_1);
|
||||
clickOnView(R.id.pin_code_button_2);
|
||||
clickOnView(R.id.pin_code_button_3);
|
||||
clickOnView(R.id.pin_code_button_4);
|
||||
solo.sleep(1000);
|
||||
|
||||
//Enter the new one
|
||||
clickOnView(R.id.pin_code_button_2);
|
||||
clickOnView(R.id.pin_code_button_3);
|
||||
clickOnView(R.id.pin_code_button_4);
|
||||
clickOnView(R.id.pin_code_button_5);
|
||||
solo.sleep(1000);
|
||||
clickOnView(R.id.pin_code_button_2);
|
||||
clickOnView(R.id.pin_code_button_3);
|
||||
clickOnView(R.id.pin_code_button_4);
|
||||
clickOnView(R.id.pin_code_button_5);
|
||||
solo.waitForActivity(MainActivity.class);
|
||||
solo.assertCurrentActivity("MainActivity", MainActivity.class);
|
||||
|
||||
//Go to unlock
|
||||
clickOnView(R.id.button_unlock_pin);
|
||||
solo.waitForActivity(CustomPinActivity.class);
|
||||
solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class);
|
||||
|
||||
//Enter the code
|
||||
clickOnView(R.id.pin_code_button_2);
|
||||
clickOnView(R.id.pin_code_button_3);
|
||||
clickOnView(R.id.pin_code_button_4);
|
||||
clickOnView(R.id.pin_code_button_5);
|
||||
|
||||
//Check view
|
||||
solo.waitForActivity(MainActivity.class);
|
||||
solo.assertCurrentActivity("MainActivity", MainActivity.class);
|
||||
}
|
||||
|
||||
public void testPinLockAfterDefaultTimeout() {
|
||||
enablePin();
|
||||
|
||||
//Go to NotLockedActivity
|
||||
solo.sleep(1000);
|
||||
clickOnView(R.id.button_not_locked);
|
||||
solo.waitForActivity(NotLockedActivity.class);
|
||||
solo.assertCurrentActivity("NotLockedActivity", NotLockedActivity.class);
|
||||
|
||||
//Set the last time to now - 11sec
|
||||
setMillis(System.currentTimeMillis() - (1000 * 15));
|
||||
solo.getCurrentActivity().finish();
|
||||
|
||||
//Check view
|
||||
solo.waitForActivity(CustomPinActivity.class);
|
||||
solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class);
|
||||
solo.sleep(1000);
|
||||
}
|
||||
|
||||
public void testPinLockAfterCustomTimeout() {
|
||||
enablePin();
|
||||
|
||||
//Set to 3minutes
|
||||
LockManager.getInstance().getAppLock().setTimeout(1000 * 60 * 3);
|
||||
|
||||
//Go to NotLockedActivity
|
||||
clickOnView(R.id.button_not_locked);
|
||||
solo.waitForActivity(NotLockedActivity.class);
|
||||
solo.assertCurrentActivity("NotLockedActivity", NotLockedActivity.class);
|
||||
|
||||
//Set the last time to now - 11sec
|
||||
setMillis(System.currentTimeMillis() - (1000 * 11));
|
||||
solo.getCurrentActivity().finish();
|
||||
|
||||
//Check view
|
||||
solo.waitForActivity(MainActivity.class);
|
||||
solo.assertCurrentActivity("MainActivity", MainActivity.class);
|
||||
solo.sleep(1000);
|
||||
|
||||
//Go to NotLockedActivity
|
||||
clickOnView(R.id.button_not_locked);
|
||||
solo.waitForActivity(NotLockedActivity.class);
|
||||
solo.assertCurrentActivity("NotLockedActivity", NotLockedActivity.class);
|
||||
|
||||
//Set the last time to now - 6minutes
|
||||
setMillis(System.currentTimeMillis() - (1000 * 60 * 6));
|
||||
solo.getCurrentActivity().finish();
|
||||
|
||||
//Check view
|
||||
solo.waitForActivity(CustomPinActivity.class);
|
||||
solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class);
|
||||
solo.sleep(1000);
|
||||
}
|
||||
|
||||
public void testPinLockWithBackgroundTimeout() {
|
||||
enablePin();
|
||||
|
||||
// Set the option to use timeout in background only
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
SharedPreferences.Editor editor = sharedPref.edit();
|
||||
editor.putBoolean(ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY, true);
|
||||
editor.apply();
|
||||
|
||||
//Go to NotLockedActivity
|
||||
solo.sleep(1000);
|
||||
clickOnView(R.id.button_not_locked);
|
||||
solo.waitForActivity(NotLockedActivity.class);
|
||||
solo.assertCurrentActivity("NotLockedActivity", NotLockedActivity.class);
|
||||
|
||||
//Set the last time to now - 15sec
|
||||
setMillis(System.currentTimeMillis() - (1000 * 15));
|
||||
solo.getCurrentActivity().finish();
|
||||
|
||||
//Check view
|
||||
solo.waitForActivity(MainActivity.class);
|
||||
solo.assertCurrentActivity("MainActivity", MainActivity.class);
|
||||
solo.sleep(1000);
|
||||
}
|
||||
|
||||
public void testBackButton() {
|
||||
enablePin();
|
||||
|
||||
//Go to unlock
|
||||
clickOnView(R.id.button_unlock_pin);
|
||||
solo.waitForActivity(CustomPinActivity.class);
|
||||
solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class);
|
||||
|
||||
solo.goBack();
|
||||
solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class);
|
||||
|
||||
//reset
|
||||
clickOnView(R.id.pin_code_button_1);
|
||||
clickOnView(R.id.pin_code_button_2);
|
||||
clickOnView(R.id.pin_code_button_3);
|
||||
clickOnView(R.id.pin_code_button_4);
|
||||
solo.sleep(1000);
|
||||
|
||||
//Go to change
|
||||
clickOnView(R.id.button_change_pin);
|
||||
solo.waitForActivity(CustomPinActivity.class);
|
||||
solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class);
|
||||
|
||||
solo.goBack();
|
||||
solo.assertCurrentActivity("MainActivity", MainActivity.class);
|
||||
}
|
||||
|
||||
public void testDisablingFingerprintReader() {
|
||||
enablePin();
|
||||
|
||||
// Disable fingerprint reader.
|
||||
LockManager.getInstance().getAppLock().setFingerprintAuthEnabled(false);
|
||||
|
||||
// Go to unlock.
|
||||
clickOnView(R.id.button_unlock_pin);
|
||||
solo.waitForActivity(CustomPinActivity.class);
|
||||
solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class);
|
||||
|
||||
// Make sure the fingerprint views are gone.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_imageview).getVisibility());
|
||||
assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_textview).getVisibility());
|
||||
}
|
||||
|
||||
// Make sure pin unlocking still works.
|
||||
clickOnView(R.id.pin_code_button_1);
|
||||
clickOnView(R.id.pin_code_button_2);
|
||||
clickOnView(R.id.pin_code_button_3);
|
||||
clickOnView(R.id.pin_code_button_4);
|
||||
solo.waitForActivity(MainActivity.class);
|
||||
solo.assertCurrentActivity("MainActivity", MainActivity.class);
|
||||
}
|
||||
|
||||
private void enablePin() {
|
||||
removePrefsAndGoToEnable();
|
||||
|
||||
clickOnView(R.id.pin_code_button_1);
|
||||
clickOnView(R.id.pin_code_button_2);
|
||||
clickOnView(R.id.pin_code_button_3);
|
||||
clickOnView(R.id.pin_code_button_4);
|
||||
solo.sleep(1000);
|
||||
clickOnView(R.id.pin_code_button_1);
|
||||
clickOnView(R.id.pin_code_button_2);
|
||||
clickOnView(R.id.pin_code_button_3);
|
||||
clickOnView(R.id.pin_code_button_4);
|
||||
solo.waitForActivity(MainActivity.class);
|
||||
solo.assertCurrentActivity("MainActivity", MainActivity.class);
|
||||
}
|
||||
|
||||
private void removePrefsAndGoToEnable() {
|
||||
//init
|
||||
removeAllPrefs();
|
||||
|
||||
//Go to enable
|
||||
if (solo.getCurrentActivity() instanceof MainActivity) {
|
||||
clickOnView(R.id.button_enable_pin);
|
||||
solo.waitForActivity(CustomPinActivity.class);
|
||||
solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class);
|
||||
solo.waitForText("1");
|
||||
}
|
||||
}
|
||||
}
|
34
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="lollipin.orangegangsters.github.com.lollipin" >
|
||||
|
||||
<application
|
||||
android:name="com.github.omadahealth.lollipin.CustomApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
<activity
|
||||
android:name="com.github.omadahealth.lollipin.MainActivity"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.github.omadahealth.lollipin.NotLockedActivity"
|
||||
android:label="@string/app_name" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.github.omadahealth.lollipin.CustomPinActivity"
|
||||
android:label="@string/app_name" >
|
||||
</activity>
|
||||
<activity android:name="com.github.omadahealth.lollipin.LockedCompatActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppThemeNoActionbar">
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,23 @@
|
||||
package com.github.omadahealth.lollipin;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.github.omadahealth.lollipin.lib.managers.LockManager;
|
||||
|
||||
import lollipin.orangegangsters.github.com.lollipin.R;
|
||||
|
||||
/**
|
||||
* Created by oliviergoutay on 1/14/15.
|
||||
*/
|
||||
public class CustomApplication extends Application {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
LockManager<CustomPinActivity> lockManager = LockManager.getInstance();
|
||||
lockManager.enableAppLock(this, CustomPinActivity.class);
|
||||
lockManager.getAppLock().setLogoId(R.drawable.security_lock);
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package com.github.omadahealth.lollipin;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.github.omadahealth.lollipin.lib.managers.AppLockActivity;
|
||||
|
||||
import lollipin.orangegangsters.github.com.lollipin.R;
|
||||
import uk.me.lewisdeane.ldialogs.BaseDialog;
|
||||
import uk.me.lewisdeane.ldialogs.CustomDialog;
|
||||
|
||||
/**
|
||||
* Created by oliviergoutay on 1/14/15.
|
||||
*/
|
||||
public class CustomPinActivity extends AppLockActivity {
|
||||
|
||||
@Override
|
||||
public void showForgotDialog() {
|
||||
Resources res = getResources();
|
||||
// Create the builder with required paramaters - Context, Title, Positive Text
|
||||
CustomDialog.Builder builder = new CustomDialog.Builder(this,
|
||||
res.getString(R.string.activity_dialog_title),
|
||||
res.getString(R.string.activity_dialog_accept));
|
||||
builder.content(res.getString(R.string.activity_dialog_content));
|
||||
builder.negativeText(res.getString(R.string.activity_dialog_decline));
|
||||
|
||||
//Set theme
|
||||
builder.darkTheme(false);
|
||||
builder.typeface(Typeface.SANS_SERIF);
|
||||
builder.positiveColor(res.getColor(R.color.light_blue_500)); // int res, or int colorRes parameter versions available as well.
|
||||
builder.negativeColor(res.getColor(R.color.light_blue_500));
|
||||
builder.rightToLeft(false); // Enables right to left positioning for languages that may require so.
|
||||
builder.titleAlignment(BaseDialog.Alignment.CENTER);
|
||||
builder.buttonAlignment(BaseDialog.Alignment.CENTER);
|
||||
builder.setButtonStacking(false);
|
||||
|
||||
//Set text sizes
|
||||
builder.titleTextSize((int) res.getDimension(R.dimen.activity_dialog_title_size));
|
||||
builder.contentTextSize((int) res.getDimension(R.dimen.activity_dialog_content_size));
|
||||
builder.positiveButtonTextSize((int) res.getDimension(R.dimen.activity_dialog_positive_button_size));
|
||||
builder.negativeButtonTextSize((int) res.getDimension(R.dimen.activity_dialog_negative_button_size));
|
||||
|
||||
//Build the dialog.
|
||||
CustomDialog customDialog = builder.build();
|
||||
customDialog.setCanceledOnTouchOutside(false);
|
||||
customDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||
customDialog.setClickListener(new CustomDialog.ClickListener() {
|
||||
@Override
|
||||
public void onConfirmClick() {
|
||||
Toast.makeText(getApplicationContext(), "Yes", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelClick() {
|
||||
Toast.makeText(getApplicationContext(), "Cancel", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
// Show the dialog.
|
||||
customDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPinFailure(int attempts) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPinSuccess(int attempts) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPinLength() {
|
||||
return super.getPinLength();//you can override this method to change the pin length from the default 4
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.github.omadahealth.lollipin;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import com.github.omadahealth.lollipin.lib.PinCompatActivity;
|
||||
import lollipin.orangegangsters.github.com.lollipin.R;
|
||||
|
||||
/**
|
||||
* Created by callmepeanut on 16-1-14.
|
||||
*/
|
||||
public class LockedCompatActivity extends PinCompatActivity{
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_compat_locked);
|
||||
initView();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.id_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
toolbar.setTitle("Title");
|
||||
toolbar.setTitleTextColor(getResources().getColor(android.R.color.white));
|
||||
toolbar.setSubtitle("SubTitle");
|
||||
toolbar.setSubtitleTextColor(getResources().getColor(android.R.color.white));
|
||||
toolbar.setLogo(R.drawable.ic_launcher);
|
||||
toolbar.setNavigationIcon(R.drawable.ic_menu_white_36dp);
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.github.omadahealth.lollipin;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.github.omadahealth.lollipin.lib.PinActivity;
|
||||
import com.github.omadahealth.lollipin.lib.managers.AppLock;
|
||||
|
||||
import lollipin.orangegangsters.github.com.lollipin.R;
|
||||
|
||||
|
||||
public class MainActivity extends PinActivity implements View.OnClickListener {
|
||||
|
||||
private static final int REQUEST_CODE_ENABLE = 11;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
this.findViewById(R.id.button_enable_pin).setOnClickListener(this);
|
||||
this.findViewById(R.id.button_change_pin).setOnClickListener(this);
|
||||
this.findViewById(R.id.button_unlock_pin).setOnClickListener(this);
|
||||
this.findViewById(R.id.button_compat_locked).setOnClickListener(this);
|
||||
this.findViewById(R.id.button_not_locked).setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(MainActivity.this, CustomPinActivity.class);
|
||||
switch (v.getId()) {
|
||||
case R.id.button_enable_pin:
|
||||
intent.putExtra(AppLock.EXTRA_TYPE, AppLock.ENABLE_PINLOCK);
|
||||
startActivityForResult(intent, REQUEST_CODE_ENABLE);
|
||||
break;
|
||||
case R.id.button_change_pin:
|
||||
intent.putExtra(AppLock.EXTRA_TYPE, AppLock.CHANGE_PIN);
|
||||
startActivity(intent);
|
||||
break;
|
||||
case R.id.button_unlock_pin:
|
||||
intent.putExtra(AppLock.EXTRA_TYPE, AppLock.UNLOCK_PIN);
|
||||
startActivity(intent);
|
||||
break;
|
||||
case R.id.button_compat_locked:
|
||||
Intent intent2 = new Intent(MainActivity.this, LockedCompatActivity.class);
|
||||
startActivity(intent2);
|
||||
break;
|
||||
case R.id.button_not_locked:
|
||||
Intent intent3 = new Intent(MainActivity.this, NotLockedActivity.class);
|
||||
startActivity(intent3);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
switch (requestCode){
|
||||
case REQUEST_CODE_ENABLE:
|
||||
Toast.makeText(this, "PinCode enabled", Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.github.omadahealth.lollipin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
import lollipin.orangegangsters.github.com.lollipin.R;
|
||||
|
||||
/**
|
||||
* Created by oliviergoutay on 1/13/15.
|
||||
*/
|
||||
public class NotLockedActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_not_locked);
|
||||
}
|
||||
|
||||
}
|
BIN
app/src/main/res/drawable-hdpi/ic_menu_white_36dp.png
Normal file
After Width: | Height: | Size: 108 B |
BIN
app/src/main/res/drawable-hdpi/icon.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_menu_white_36dp.png
Normal file
After Width: | Height: | Size: 92 B |
BIN
app/src/main/res/drawable-mdpi/icon.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_menu_white_36dp.png
Normal file
After Width: | Height: | Size: 95 B |
BIN
app/src/main/res/drawable-xhdpi/icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/drawable-xhdpi/security_lock.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_menu_white_36dp.png
Normal file
After Width: | Height: | Size: 100 B |
BIN
app/src/main/res/drawable-xxhdpi/icon.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_menu_white_36dp.png
Normal file
After Width: | Height: | Size: 110 B |
20
app/src/main/res/layout/activity_compat_locked.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/id_toolbar"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:background="@color/material_blue_500"
|
||||
android:minHeight="?attr/actionBarSize"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/activity_horizontal_margin"
|
||||
android:text="@string/activity_appcompat_message"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
</LinearLayout>
|
40
app/src/main/res/layout/activity_main.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_enable_pin"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ustaw pin"/>
|
||||
<Button
|
||||
android:id="@+id/button_change_pin"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/button_enable_pin"
|
||||
android:text="Zmień pin"/>
|
||||
<Button
|
||||
android:id="@+id/button_unlock_pin"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/button_change_pin"
|
||||
android:text="Check"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_compat_locked"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/button_unlock_pin"
|
||||
android:text="Go to PinCompatActivity"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_not_locked"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/button_compat_locked"
|
||||
android:text="Przejdź do aplikacji bez pinu"/>
|
||||
|
||||
</RelativeLayout>
|
13
app/src/main/res/layout/activity_not_locked.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/activity_not_locked_text" />
|
||||
|
||||
</RelativeLayout>
|
6
app/src/main/res/menu/menu_main.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
|
||||
<item android:id="@+id/action_settings" android:title="@string/action_settings"
|
||||
android:orderInCategory="100" app:showAsAction="never" />
|
||||
</menu>
|
BIN
app/src/main/res/raw/github_gif.gif
Normal file
After Width: | Height: | Size: 312 KiB |
BIN
app/src/main/res/raw/github_gif2.gif
Normal file
After Width: | Height: | Size: 100 KiB |
6
app/src/main/res/values-v21/styles.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="AppBaseTheme">
|
||||
<item name="android:statusBarColor">@color/material_blue_700</item>
|
||||
</style>
|
||||
</resources>
|
6
app/src/main/res/values-w820dp/dimens.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||
(such as screen margins) for screens with more than 820dp of available width. This
|
||||
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||
</resources>
|
6
app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="material_blue_500">#009688</color>
|
||||
<color name="material_blue_700">#00796B</color>
|
||||
<color name="material_green_A200">#FD87A9</color>
|
||||
</resources>
|
10
app/src/main/res/values/dimens.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
|
||||
<dimen name="activity_dialog_title_size">9sp</dimen>
|
||||
<dimen name="activity_dialog_content_size">5sp</dimen>
|
||||
<dimen name="activity_dialog_positive_button_size">6sp</dimen>
|
||||
<dimen name="activity_dialog_negative_button_size">6sp</dimen>
|
||||
</resources>
|
14
app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">LolliPin</string>
|
||||
<string name="hello_world">Hello world!</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="activity_not_locked_text">Nothing should happen on this page, here for test purpose</string>
|
||||
<string name="activity_dialog_title">Zapomniałeś pinu?</string>
|
||||
<string name="activity_dialog_accept">OK</string>
|
||||
<string name="activity_dialog_decline">ANULUJ</string>
|
||||
<string name="activity_dialog_content">Tap \'OK\' to log out, when you log back in you\'ll be able to create a new Pincode.\n\nIf you remember your Pincode, tap \'Cancel\' to go back.</string>
|
||||
<string name="activity_appcompat_message">I am a PinCompatActivity!</string>
|
||||
|
||||
</resources>
|
23
app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="AppBaseTheme">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
|
||||
<style name="AppBaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- customize the color palette -->
|
||||
<item name="colorPrimary">@color/material_blue_500</item>
|
||||
<item name="colorPrimaryDark">@color/material_blue_700</item>
|
||||
<item name="colorAccent">@color/material_green_A200</item>
|
||||
</style>
|
||||
|
||||
<style name="AppThemeNoActionbar" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- customize the color palette -->
|
||||
<item name="colorPrimary">@color/material_blue_500</item>
|
||||
<item name="colorPrimaryDark">@color/material_blue_700</item>
|
||||
<item name="colorAccent">@color/material_green_A200</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
23
build.gradle
Normal file
@ -0,0 +1,23 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.3.3'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven{
|
||||
url "https://github.com/omadahealth/omada-nexus/raw/master/release"
|
||||
}
|
||||
jcenter()
|
||||
|
||||
}
|
||||
}
|
39
gradle.properties
Normal file
@ -0,0 +1,39 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
#VERSION_NAME=1.0.0-SNAPSHOT
|
||||
VERSION_NAME=2.1.0
|
||||
VERSION_CODE=29
|
||||
GROUP=com.github.omadahealth
|
||||
|
||||
POM_DESCRIPTION=Pin code/ fingerprint based lockscreen for you app
|
||||
POM_URL=https://github.com/omadahealth/LolliPin
|
||||
POM_SCM_URL=https://github.com/omadahealth/LolliPin.git
|
||||
POM_SCM_CONNECTION=https://github.com/omadahealth/LolliPin.git
|
||||
POM_SCM_DEV_CONNECTION=https://github.com/omadahealth/LolliPin.git
|
||||
POM_LICENCE_NAME=The MIT License (MIT)
|
||||
POM_LICENCE_URL=https://github.com/omadahealth/LolliPin/blob/master/LICENSE
|
||||
POM_LICENCE_DIST=https://github.com/omadahealth/LolliPin/blob/master/LICENSE
|
||||
POM_DEVELOPER_ID=olivierg13 & stoyand & daespark
|
||||
POM_DEVELOPER_NAME=Olivier Goutay & Stoyan Dimitrov & Dae Park
|
||||
|
||||
|
||||
POM_NAME=LolliPin Library
|
||||
POM_ARTIFACT_ID=lollipin
|
||||
POM_PACKAGING=aar
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#Mon Apr 17 11:56:01 PDT 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
|
164
gradlew
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
gradlew.bat
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
1
lib/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
62
lib/build.gradle
Normal file
@ -0,0 +1,62 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
buildToolsVersion '26.0.1'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 2
|
||||
versionName VERSION_NAME
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
//RippleView
|
||||
compile 'com.github.traex.rippleeffect:ripple:1.3.1-OG'
|
||||
//TypefaceView
|
||||
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"
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://maven.google.com'
|
||||
// Alternative URL is 'https://dl.google.com/dl/android/maven2/'
|
||||
}
|
||||
}
|
||||
|
||||
//gradle clean build uploadArchives
|
||||
apply from: 'https://raw.github.com/omadahealth/omada-nexus/master/gradle-mvn-push/gradle-mvn-push.gradle'
|
||||
|
||||
//task androidSourcesJar(type: Jar) {
|
||||
// classifier = 'sources'
|
||||
// from android.sourceSets.main.java.sourceFiles
|
||||
//}
|
||||
//artifacts {
|
||||
// archives androidSourcesJar
|
||||
//}
|
||||
//
|
||||
//apply plugin: 'maven'
|
||||
//uploadArchives {
|
||||
// repositories {
|
||||
// mavenDeployer {
|
||||
// repository(url: uri("../../omada-nexus/release"))
|
||||
// snapshotRepository(url: uri("../../omada-nexus/release"))
|
||||
// pom.groupId = GROUP
|
||||
// pom.artifactId = POM_ARTIFACT_ID
|
||||
// pom.version = VERSION_NAME
|
||||
// }
|
||||
// }
|
||||
//}
|
17
lib/proguard-rules.pro
vendored
Normal file
@ -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 *;
|
||||
#}
|
@ -0,0 +1,13 @@
|
||||
package com.github.omadahealth.lollipin.lib;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
10
lib/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.github.omadahealth.lollipin.lib">
|
||||
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
<application android:allowBackup="true" android:label="@string/app_name" >
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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<String> mIgnoredActivities;
|
||||
|
||||
public AppLock() {
|
||||
mIgnoredActivities = new HashSet<String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
@ -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.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<Integer>} of {@link AppLock} types which are backable
|
||||
*/
|
||||
public List<Integer> 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<? extends AppLockActivity> getCustomAppLockActivityClass() {
|
||||
return this.getClass();
|
||||
}
|
||||
}
|
@ -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<T extends AppLockActivity> 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<T> 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<? extends AppLockActivity> activityClass) {
|
||||
synchronized (LockManager.class) {
|
||||
if (mInstance == null) {
|
||||
mInstance = new AppLockImpl<>(context, activityClass);
|
||||
}
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
private AppLockImpl(Context context, Class<T> 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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.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();
|
||||
}
|
||||
}
|
@ -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<T extends AppLockActivity> {
|
||||
|
||||
/**
|
||||
* 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<T> 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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<KeyboardButtonView> 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<ImageView> 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<ImageView> 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
3
lib/src/main/res/anim/cycle5.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:cycles="5" />
|
5
lib/src/main/res/anim/nothing.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="300"
|
||||
android:fromXDelta="0"
|
||||
android:toXDelta="0" />
|
6
lib/src/main/res/anim/shake.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="1000"
|
||||
android:fromXDelta="0"
|
||||
android:interpolator="@anim/cycle5"
|
||||
android:toXDelta="10" />
|
6
lib/src/main/res/anim/slide_down.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="500"
|
||||
android:fromYDelta="0"
|
||||
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
|
||||
android:toYDelta="100%" />
|
BIN
lib/src/main/res/drawable-hdpi/ic_backspace_grey600_24dp.png
Normal file
After Width: | Height: | Size: 441 B |
BIN
lib/src/main/res/drawable-hdpi/ic_fp_40px.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
lib/src/main/res/drawable-hdpi/tile.9.png
Normal file
After Width: | Height: | Size: 196 B |
BIN
lib/src/main/res/drawable-mdpi/ic_backspace_grey600_24dp.png
Normal file
After Width: | Height: | Size: 333 B |
BIN
lib/src/main/res/drawable-mdpi/ic_fp_40px.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
lib/src/main/res/drawable-xhdpi/ic_backspace_grey600_24dp.png
Normal file
After Width: | Height: | Size: 529 B |
BIN
lib/src/main/res/drawable-xhdpi/ic_fp_40px.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
lib/src/main/res/drawable-xxhdpi/ic_backspace_grey600_24dp.png
Normal file
After Width: | Height: | Size: 699 B |
BIN
lib/src/main/res/drawable-xxhdpi/ic_fp_40px.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
lib/src/main/res/drawable-xxxhdpi/ic_backspace_grey600_24dp.png
Normal file
After Width: | Height: | Size: 942 B |
BIN
lib/src/main/res/drawable-xxxhdpi/ic_fp_40px.png
Normal file
After Width: | Height: | Size: 16 KiB |
28
lib/src/main/res/drawable/ic_fingerprint_error.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40.0dp"
|
||||
android:height="40.0dp"
|
||||
android:viewportWidth="40.0"
|
||||
android:viewportHeight="40.0">
|
||||
<path
|
||||
android:pathData="M20.0,0.0C8.96,0.0 0.0,8.95 0.0,20.0s8.96,20.0 20.0,20.0c11.04,0.0 20.0,-8.95 20.0,-20.0S31.04,0.0 20.0,0.0z"
|
||||
android:fillColor="#F4511E"/>
|
||||
<path
|
||||
android:pathData="M21.33,29.33l-2.67,0.0l0.0,-2.67l2.67,0.0L21.33,29.33zM21.33,22.67l-2.67,0.0l0.0,-12.0l2.67,0.0L21.33,22.67z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
28
lib/src/main/res/drawable/ic_fingerprint_success.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40.0dp"
|
||||
android:height="40.0dp"
|
||||
android:viewportWidth="40.0"
|
||||
android:viewportHeight="40.0">
|
||||
<path
|
||||
android:pathData="M20.0,20.0m-20.0,0.0a20.0,20.0 0.0,1.0 1.0,40.0 0.0a20.0,20.0 0.0,1.0 1.0,-40.0 0.0"
|
||||
android:fillColor="#009688"/>
|
||||
<path
|
||||
android:pathData="M11.2,21.41l1.63,-1.619999 4.17,4.169998 10.59,-10.589999 1.619999,1.63 -12.209999,12.209999z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
15
lib/src/main/res/drawable/pin_code_round_empty.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval" >
|
||||
|
||||
<solid
|
||||
android:color="@color/light_gray" >
|
||||
</solid>
|
||||
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/dark_gray" >
|
||||
</stroke>
|
||||
|
||||
</shape>
|
15
lib/src/main/res/drawable/pin_code_round_full.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval" >
|
||||
|
||||
<solid
|
||||
android:color="@color/light_blue_500" >
|
||||
</solid>
|
||||
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/light_blue_500" >
|
||||
</stroke>
|
||||
|
||||
</shape>
|
97
lib/src/main/res/layout/activity_pin_code.xml
Normal file
@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white"
|
||||
android:fillViewport="true">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/activity_pin_code_padding"
|
||||
android:paddingTop="@dimen/activity_pin_code_padding">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/pin_code_logo_imageview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginBottom="@dimen/pin_code_logo_margin"
|
||||
android:visibility="invisible"
|
||||
tools:src="@android:drawable/sym_def_app_icon"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.github.omadahealth.typefaceview.TypefaceTextView
|
||||
android:id="@+id/pin_code_step_textview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/pin_code_logo_imageview"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:textColor="@color/dark_grey_color"
|
||||
android:textSize="@dimen/pin_code_step_text_size" />
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.PinCodeRoundView
|
||||
android:id="@+id/pin_code_round_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/pin_code_step_textview"
|
||||
android:layout_marginBottom="@dimen/pin_code_elements_margin"
|
||||
android:layout_marginTop="@dimen/pin_code_round_top_margin" />
|
||||
|
||||
<com.github.omadahealth.typefaceview.TypefaceTextView
|
||||
android:id="@+id/pin_code_forgot_textview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/pin_code_round_view"
|
||||
android:layout_centerInParent="true"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/dark_grey_color"
|
||||
android:textSize="@dimen/pin_code_forgot_text_size"
|
||||
tools:text="@string/pin_code_forgot_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/pin_code_gray_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/pin_code_forgot_textview"
|
||||
android:layout_marginBottom="@dimen/light_gray_bar_margin_bottom"
|
||||
android:layout_marginLeft="@dimen/light_gray_bar_margin_sides"
|
||||
android:layout_marginRight="@dimen/light_gray_bar_margin_sides"
|
||||
android:layout_marginTop="@dimen/light_gray_bar_margin_top"
|
||||
android:background="@color/light_gray_bar"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.KeyboardView
|
||||
android:id="@+id/pin_code_keyboard_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@+id/pin_code_fingerprint_imageview"
|
||||
android:layout_below="@+id/pin_code_gray_bar" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/pin_code_fingerprint_imageview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@+id/pin_code_fingerprint_textview"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="20dp"
|
||||
android:src="@drawable/ic_fp_40px" />
|
||||
|
||||
<com.github.omadahealth.typefaceview.TypefaceTextView
|
||||
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:maxLines="1"
|
||||
android:textColor="@color/dark_grey_color"
|
||||
android:textSize="@dimen/pin_code_forgot_text_size"
|
||||
tools:text="@string/pin_code_fingerprint_text" />
|
||||
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
127
lib/src/main/res/layout/view_keyboard.xml
Normal file
@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout android:id="@+id/pin_code_first_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="3" >
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.KeyboardButtonView
|
||||
android:id="@+id/pin_code_button_1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:lp_keyboard_button_text="@string/button1_large_text" />
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.KeyboardButtonView
|
||||
android:id="@+id/pin_code_button_2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:lp_keyboard_button_text="@string/button2_large_text" />
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.KeyboardButtonView
|
||||
android:id="@+id/pin_code_button_3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:lp_keyboard_button_text="@string/button3_large_text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout android:id="@+id/pin_code_second_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_below="@+id/pin_code_first_row"
|
||||
android:weightSum="3">
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.KeyboardButtonView
|
||||
android:id="@+id/pin_code_button_4"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:lp_keyboard_button_text="@string/button4_large_text" />
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.KeyboardButtonView
|
||||
android:id="@+id/pin_code_button_5"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:lp_keyboard_button_text="@string/button5_large_text" />
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.KeyboardButtonView
|
||||
android:id="@+id/pin_code_button_6"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:lp_keyboard_button_text="@string/button6_large_text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout android:id="@+id/pin_code_third_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_below="@+id/pin_code_second_row"
|
||||
android:weightSum="3">
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.KeyboardButtonView
|
||||
android:id="@+id/pin_code_button_7"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:lp_keyboard_button_text="@string/button7_large_text" />
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.KeyboardButtonView
|
||||
android:id="@+id/pin_code_button_8"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:lp_keyboard_button_text="@string/button8_large_text" />
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.KeyboardButtonView
|
||||
android:id="@+id/pin_code_button_9"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:lp_keyboard_button_text="@string/button9_large_text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout android:id="@+id/pin_code_fourth_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_below="@+id/pin_code_third_row"
|
||||
android:weightSum="3">
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.KeyboardButtonView
|
||||
android:id="@+id/pin_code_button_10"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:lp_keyboard_button_ripple_enabled="false" />
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.KeyboardButtonView
|
||||
android:id="@+id/pin_code_button_0"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:lp_keyboard_button_text="@string/button11_large_text" />
|
||||
|
||||
<com.github.omadahealth.lollipin.lib.views.KeyboardButtonView
|
||||
android:id="@+id/pin_code_button_clear"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:lp_keyboard_button_image="@drawable/ic_backspace_grey600_24dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
31
lib/src/main/res/layout/view_keyboard_button.xml
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.andexert.library.RippleView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/pin_code_keyboard_button_ripple"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingTop="@dimen/keyboard_button_padding"
|
||||
android:paddingBottom="@dimen/keyboard_button_padding"
|
||||
app:rv_centered="true"
|
||||
app:rv_rippleDuration="@integer/ripple_effect_duration"
|
||||
app:rv_ripplePadding="@dimen/keyboard_button_ripple_padding"
|
||||
app:rv_color="@android:color/black" >
|
||||
|
||||
<com.github.omadahealth.typefaceview.TypefaceTextView
|
||||
android:id="@+id/keyboard_button_textview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:textColor="@color/dark_grey_color"
|
||||
android:textSize="@dimen/keyboard_button_text_size"
|
||||
android:maxLines="1"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/keyboard_button_imageview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</com.andexert.library.RippleView>
|
11
lib/src/main/res/layout/view_round.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.github.omadahealth.lollipin.lib.views.SquareImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/pin_code_round"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/pin_code_round_size"
|
||||
android:layout_margin="@dimen/pin_code_round_margin"
|
||||
android:layout_weight="1"
|
||||
android:maxHeight="@dimen/pin_code_round_size"
|
||||
android:maxWidth="@dimen/pin_code_round_size"
|
||||
tools:src="@drawable/pin_code_round_empty" />
|
9
lib/src/main/res/layout/view_round_pin_code.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/round_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
</LinearLayout>
|
14
lib/src/main/res/values-id/strings.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<resources>
|
||||
|
||||
<string name="pin_code_forgot_text">Lupa?</string>
|
||||
<string name="pin_code_step_disable">Matikan %d-digit Kode pin</string>
|
||||
<string name="pin_code_step_create">Buat %d-digit Kode pin</string>
|
||||
<string name="pin_code_step_change">Masukan %d-digit Kode pin Anda</string>
|
||||
<string name="pin_code_step_unlock">Masukan %d-digit Kode pin Anda</string>
|
||||
<string name="pin_code_step_enable_confirm">Pastikan %d-digit Kode pin Anda</string>
|
||||
|
||||
<string name="pin_code_fingerprint_text">Tanda tangan</string>
|
||||
<string name="pin_code_fingerprint_success">Tanda tangan dikenali</string>
|
||||
<string name="pin_code_fingerprint_not_recognized">Tanda tangan tidak dikenali. Coba lagi</string>
|
||||
|
||||
</resources>
|
14
lib/src/main/res/values-ko/strings.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="pin_code_forgot_text">잊어버렸나요?</string>
|
||||
<string name="pin_code_step_disable">%d-자리 핀코드 해제</string>
|
||||
<string name="pin_code_step_create">%d-자리 핀코드 생성</string>
|
||||
<string name="pin_code_step_change">입력한 %d-자리 핀코드를 입력하세요</string>
|
||||
<string name="pin_code_step_unlock">%d-자리 핀코드를 입력하세요</string>
|
||||
<string name="pin_code_step_enable_confirm">%d-자리 핀코드를 한번 더 입력하세요</string>
|
||||
|
||||
<string name="pin_code_fingerprint_text">지문인식</string>
|
||||
<string name="pin_code_fingerprint_success">지문을 인식했습니다</string>
|
||||
<string name="pin_code_fingerprint_not_recognized">지문을 인식하지 못 했습니다. 다시 시도해 주세요</string>
|
||||
|
||||
</resources>
|
12
lib/src/main/res/values-pt/strings.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="pin_code_fingerprint_not_recognized">Digital não reconhecida. Tente novamente</string>
|
||||
<string name="pin_code_fingerprint_success">Digital reconhecida</string>
|
||||
<string name="pin_code_fingerprint_text">Digital</string>
|
||||
<string name="pin_code_forgot_text">Esqueceu?</string>
|
||||
<string name="pin_code_step_change">Insira seu PIN atual de %d dígitos</string>
|
||||
<string name="pin_code_step_create">Criar um PIN de %d dígitos</string>
|
||||
<string name="pin_code_step_disable">Desabilitar PIN de %d dígitos</string>
|
||||
<string name="pin_code_step_enable_confirm">Confirme seu PIN de %d dígitos</string>
|
||||
<string name="pin_code_step_unlock">Insira seu PIN de %d dígitos</string>
|
||||
</resources>
|
12
lib/src/main/res/values-ru/strings.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<resources>
|
||||
<string name="pin_code_forgot_text">Забыли пароль?</string>
|
||||
<string name="pin_code_step_disable">Отключите %d-значный пин-код</string>
|
||||
<string name="pin_code_step_create">Создайте %d-значный пин-код</string>
|
||||
<string name="pin_code_step_change">Введите свой %d-значный пин-код</string>
|
||||
<string name="pin_code_step_unlock">Введите свой %d-значный пин-код</string>
|
||||
<string name="pin_code_step_enable_confirm">Подтвердите ваш %d-значный пин-код</string>
|
||||
|
||||
<string name="pin_code_fingerprint_text">Отпечаток пальца</string>
|
||||
<string name="pin_code_fingerprint_success">Отпечаток принят</string>
|
||||
<string name="pin_code_fingerprint_not_recognized">Отпечаток не принят. Попробуйте снова.</string>
|
||||
</resources>
|
18
lib/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="PinCodeView">
|
||||
<attr name="lp_empty_pin_dot" format="integer" />
|
||||
<attr name="lp_full_pin_dot" format="integer" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="KeyboardButtonView">
|
||||
<attr name="lp_keyboard_button_text" format="string" />
|
||||
<attr name="lp_keyboard_button_image" format="integer" />
|
||||
<attr name="lp_keyboard_button_ripple_enabled" format="boolean" />
|
||||
<attr name="lp_pin_forgot_dialog_title" format="string" />
|
||||
<attr name="lp_pin_forgot_dialog_content" format="string" />
|
||||
<attr name="lp_pin_forgot_dialog_positive" format="string" />
|
||||
<attr name="lp_pin_forgot_dialog_negative" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
13
lib/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="dark_grey_color">#4a636f</color>
|
||||
<color name="light_blue_500">#03a9f4</color>
|
||||
<color name="blue_gray">#4b636f</color>
|
||||
<color name="light_gray">#f0f6f9</color>
|
||||
<color name="dark_gray">#9baab2</color>
|
||||
<color name="light_gray_bar">#eaf2f5</color>
|
||||
|
||||
<color name="warning_color">#f4511e</color>
|
||||
<color name="hint_color">#42000000</color>
|
||||
<color name="success_color">#009688</color>
|
||||
</resources>
|
21
lib/src/main/res/values/dimens.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="keyboard_button_text_size">30sp</dimen>
|
||||
<dimen name="keyboard_button_padding">15dp</dimen>
|
||||
<dimen name="keyboard_button_ripple_padding">30dp</dimen>
|
||||
<dimen name="pin_code_round_size">25dp</dimen>
|
||||
<dimen name="pin_code_round_margin">10dp</dimen>
|
||||
<dimen name="light_gray_bar_margin_top">20dp</dimen>
|
||||
<dimen name="light_gray_bar_margin_bottom">5dp</dimen>
|
||||
<dimen name="light_gray_bar_margin_sides">50dp</dimen>
|
||||
<dimen name="pin_code_forgot_text_size">16sp</dimen>
|
||||
<dimen name="pin_code_step_text_size">20sp</dimen>
|
||||
<dimen name="pin_code_elements_margin">20dp</dimen>
|
||||
<dimen name="activity_pin_code_padding">20dp</dimen>
|
||||
<dimen name="pin_code_logo_margin">30dp</dimen>
|
||||
<dimen name="pin_code_round_top_margin">10dp</dimen>
|
||||
|
||||
|
||||
|
||||
|
||||
</resources>
|
4
lib/src/main/res/values/integers.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="ripple_effect_duration">200</integer>
|
||||
</resources>
|
34
lib/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">lib</string>
|
||||
|
||||
<string name="button1_large_text" translatable="false">1</string>
|
||||
<string name="button2_large_text" translatable="false">2</string>
|
||||
<string name="button2_small_text" translatable="false">ABC</string>
|
||||
<string name="button3_large_text" translatable="false">3</string>
|
||||
<string name="button3_small_text" translatable="false">DEF</string>
|
||||
<string name="button4_large_text" translatable="false">4</string>
|
||||
<string name="button4_small_text" translatable="false">GHI</string>
|
||||
<string name="button5_large_text" translatable="false">5</string>
|
||||
<string name="button5_small_text" translatable="false">JKL</string>
|
||||
<string name="button6_large_text" translatable="false">6</string>
|
||||
<string name="button6_small_text" translatable="false">MNO</string>
|
||||
<string name="button7_large_text" translatable="false">7</string>
|
||||
<string name="button7_small_text" translatable="false">PQRS</string>
|
||||
<string name="button8_large_text" translatable="false">8</string>
|
||||
<string name="button8_small_text" translatable="false">TUV</string>
|
||||
<string name="button9_large_text" translatable="false">9</string>
|
||||
<string name="button9_small_text" translatable="false">WXYZ</string>
|
||||
<string name="button11_large_text" translatable="false">0</string>
|
||||
|
||||
<string name="pin_code_forgot_text">Forgot?</string>
|
||||
<string name="pin_code_step_disable">Disable %d-digit Pincode</string>
|
||||
<string name="pin_code_step_create">Create a %d-digit Pincode</string>
|
||||
<string name="pin_code_step_change">Enter your current %d-digit Pincode</string>
|
||||
<string name="pin_code_step_unlock">Enter your %d-digit Pincode</string>
|
||||
<string name="pin_code_step_enable_confirm">Confirm your %d-digit Pincode</string>
|
||||
|
||||
<string name="pin_code_fingerprint_text">Fingerprint</string>
|
||||
<string name="pin_code_fingerprint_success">Fingerprint recognized</string>
|
||||
<string name="pin_code_fingerprint_not_recognized">Fingerprint not recognized. Try again</string>
|
||||
|
||||
</resources>
|
1
settings.gradle
Normal file
@ -0,0 +1 @@
|
||||
include ':app', ':lib'
|