This commit is contained in:
Magdalena Wojciechowska 2018-12-05 11:29:07 +01:00
parent dddbaf37c8
commit 2de61776b5
97 changed files with 4239 additions and 0 deletions

22
LICENSE Normal file
View 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
View File

@ -0,0 +1 @@
/build

43
app/build.gradle Normal file
View 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
View 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 *;
#}

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

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

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

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

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

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

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

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

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

Binary file not shown.

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

@ -0,0 +1 @@
/build

62
lib/build.gradle Normal file
View 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
View 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 *;
#}

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

View 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%" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="ripple_effect_duration">200</integer>
</resources>

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

@ -0,0 +1 @@
include ':app', ':lib'