├── .gitignore ├── .idea └── codeStyleSettings.xml ├── Changelog.md ├── LICENSE ├── README.md ├── build.gradle ├── demo ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── venmo │ │ └── android │ │ └── pin │ │ └── pindemo │ │ └── demo │ │ ├── PinDemoActivity.java │ │ ├── PinLauncher.java │ │ └── PinSupportDemoActivity.java │ └── res │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ ├── activity_pin_demo.xml │ └── activity_pin_launcher.xml │ ├── menu │ └── pin_demo.xml │ └── values │ ├── strings.xml │ └── styles.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle └── src │ ├── androidTest │ ├── java │ │ └── com │ │ │ └── venmo │ │ │ └── android │ │ │ └── pin │ │ │ ├── PinFragmentTests.java │ │ │ ├── PinHelperTest.java │ │ │ ├── TestActivity.java │ │ │ └── TestSupportActivity.java │ └── res │ │ └── layout │ │ └── layout_pin_test.xml │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── venmo │ │ └── android │ │ └── pin │ │ ├── AsyncSaver.java │ │ ├── AsyncValidator.java │ │ ├── BaseViewController.java │ │ ├── ConfirmPinViewController.java │ │ ├── CreatePinViewController.java │ │ ├── DefaultSaver.java │ │ ├── DefaultValidator.java │ │ ├── PinDisplayType.java │ │ ├── PinFragment.java │ │ ├── PinFragmentConfiguration.java │ │ ├── PinFragmentImplement.java │ │ ├── PinListener.java │ │ ├── PinSaver.java │ │ ├── PinSupportFragment.java │ │ ├── TryDepletionListener.java │ │ ├── Validator.java │ │ ├── VerifyPinViewController.java │ │ ├── util │ │ ├── AppLifeCycleListener.java │ │ ├── PinHelper.java │ │ └── VibrationHelper.java │ │ └── view │ │ ├── PinKeyboardView.java │ │ └── PinputView.java │ └── res │ ├── drawable-xhdpi │ └── key_back.png │ ├── drawable │ └── pin_key_selector.xml │ ├── layout │ └── layout_pin_view.xml │ ├── values-v16 │ └── styles.xml │ ├── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── keyboard_number_pad.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | 28 | # IntelliJ files 29 | .idea/* 30 | *.iml 31 | !.idea/codeStyleSettings.xml 32 | -------------------------------------------------------------------------------- /.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 94 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | 6 | ## [Unreleased] 7 | 8 | ## [1.2.0] - 2016-06-21 9 | ### Added 10 | - Adhere to semantic versioning 11 | - Add pin UI support for android.support.v4.app.Fragment 12 | - Update lib build tools to 23.0.3' 13 | 14 | ## [0.2] - Jan-22-2016 15 | ### Added 16 | - Rename custom styles attrs 17 | 18 | ## [0.1] - Sept-21-2014 19 | - Initial release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Venmo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android-Pin 2 | =========== 3 | 4 | An easy drop-in PIN controller for Android 5 | 6 | Usage 7 | ===== 8 | [`PinFragment`](https://github.com/venmo/android-pin/blob/master/library/src/main/java/com/venmo/android/pin/PinFragment.java) is the primary class provided by this library. A `PinFragment` should be instantiated either for a PIN creation, or for PIN validation. 9 | 10 | ``` 11 | Fragment toShow = PinHelper.hasDefaultPinSaved(this) ? 12 | PinFragment.newInstanceForVerification() : 13 | PinFragment.newInstanceForCreation(); 14 | 15 | getFragmentManager().beginTransaction() 16 | .replace(android.R.id.container, toShow) 17 | .commit(); 18 | ``` 19 | 20 | A hosting `Activity` should implement `PinListener` to perform actions when a PIN has been created or validated. 21 | 22 | ``` 23 | public interface PinListener { 24 | public void onValidated(); 25 | public void onPinCreated(); 26 | } 27 | ``` 28 | 29 | By default, a user's PIN is saved in the default `SharedPreference` store, and validated against that store. To override this behavior, instantiate your `PinFragment` with a `PinFragmentConfiguration` that provides . An example of a configuration might be 30 | 31 | ``` 32 | PinFragmentConfiguration config = 33 | new PinFragmentConfiguration(context) 34 | .pinSaver(new PinSaver(){ 35 | public void onSave(String pin) { 36 | // ...do some saving 37 | } 38 | }).validator(new Validator(){ 39 | public boolean isValid(String submission){ 40 | boolean valid = // ...check against where you saved the pin 41 | return valid; 42 | } 43 | }); 44 | ``` 45 | 46 | Then, instantiation of your `PinFragment` might look like this 47 | 48 | ``` 49 | Fragment toShow = doesHavePinSavedSomewhere() ? 50 | PinFragment.newInstanceForVerification(config) : 51 | PinFragment.newInstanceForCreation(config); 52 | 53 | getFragmentManager().beginTransaction() 54 | .replace(android.R.id.container, toShow) 55 | .commit(); 56 | ``` 57 | 58 | In general, any time a custom `PinSaver` is defined, it should follow that a custom `Validator` is also defined. This may be more strictly enforced in the future. 59 | 60 | Asynchronous Handling 61 | ===================== 62 | A very common use case for providing an alternative `Validator` and `PinSaver` is if you persist a user's PIN remotely and validate by making a request to your server. In this case, `PinFragment` can execute your saving and checking on a background thread and show a `ProgressBar` while executing. To utilize this, pass implementations of `AsyncSaver` and `AsyncValidator` to your configuration. 63 | 64 | ``` 65 | PinFragmentConfiguration config = 66 | new PinFragmentConfiguration(context) 67 | .pinSaver(new AsyncPinSaver(){ 68 | public void onSave(String pin){ 69 | HttpClient client = //... 70 | client.savePin(pin); 71 | } 72 | }).validator(new AsyncValidator(){ 73 | public boolean isValid(String submission){ 74 | // HttpClient client = ... 75 | // boolean valid = client.comparePin(submission); 76 | return valid; 77 | } 78 | }); 79 | ``` 80 | 81 | This configuration will instruct your `PinFragment` instance to run `onSave()` and `isValid()` in the background and post to your `PinFragment.Listener` only when a PIN has been successfully created or verified, meaning you don't need to think about scheduling things to happen in the background. 82 | 83 | Support Library v4 - Fragment 84 | ============================= 85 | 86 | In order to allow use of Fragment from the support library v4, we've added a 87 | new class `PinSupportFragment` that behave exactly like `PinFragment` but use `android.support.v4.app.Fragment`. Make sure to use it if you need such behavior. 88 | 89 | Including in your project 90 | ========================= 91 | 92 | This library is hosted on Maven Central; to include add the following to your `pom.xml` 93 | 94 | ``` 95 | 96 | com.venmo.android.pin 97 | library 98 | 0.1 99 | 100 | ``` 101 | 102 | For gradle builds, add the following to your `build.gradle` 103 | 104 | ``` 105 | repositories { 106 | mavenCentral() 107 | } 108 | 109 | dependencies { 110 | compile 'com.venmo.android.pin:library:0.2@aar' 111 | } 112 | ``` 113 | 114 | Contributing 115 | ============= 116 | Contributions are encouraged! If you would like to contribute, fork this repository and send a pull request. Please make sure to follow the project code style if possible. `.iml/codeStyleSettings` is provided for your convenience in Android Studio/IntelliJ. 117 | 118 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.0.0' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | mavenCentral() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion '23.0.3' 6 | 7 | defaultConfig { 8 | applicationId "com.venmo.android.pin.pindemo.demo" 9 | minSdkVersion 15 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile project(':library') 24 | compile 'com.android.support:support-v4:23.4.0' 25 | compile 'com.android.support:appcompat-v7:23.4.0' 26 | } 27 | -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /demo/src/main/java/com/venmo/android/pin/pindemo/demo/PinDemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.venmo.android.pin.pindemo.demo; 2 | 3 | import android.app.Activity; 4 | import android.app.Fragment; 5 | import android.os.Bundle; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | import android.widget.Toast; 9 | 10 | import com.venmo.android.pin.PinListener; 11 | import com.venmo.android.pin.PinFragment; 12 | import com.venmo.android.pin.util.PinHelper; 13 | 14 | public class PinDemoActivity extends Activity implements PinListener { 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_pin_demo); 20 | Fragment toShow = PinHelper.hasDefaultPinSaved(this) ? 21 | PinFragment.newInstanceForVerification() : 22 | PinFragment.newInstanceForCreation(); 23 | 24 | getFragmentManager().beginTransaction() 25 | .replace(R.id.root, toShow, toShow.getClass().getSimpleName()) 26 | .commit(); 27 | } 28 | 29 | @Override 30 | public boolean onCreateOptionsMenu(Menu menu) { 31 | getMenuInflater().inflate(R.menu.pin_demo, menu); 32 | return true; 33 | } 34 | 35 | @Override 36 | public boolean onOptionsItemSelected(MenuItem item) { 37 | int id = item.getItemId(); 38 | if (id == R.id.action_settings) { 39 | PinHelper.resetDefaultSavedPin(this); 40 | recreate(); 41 | return true; 42 | } 43 | return super.onOptionsItemSelected(item); 44 | } 45 | 46 | @Override 47 | public void onValidated() { 48 | Toast.makeText(this, "Validated PIN!", Toast.LENGTH_SHORT).show(); 49 | recreate(); 50 | } 51 | 52 | @Override 53 | public void onPinCreated() { 54 | Toast.makeText(this, "Created PIN!", Toast.LENGTH_SHORT).show(); 55 | recreate(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /demo/src/main/java/com/venmo/android/pin/pindemo/demo/PinLauncher.java: -------------------------------------------------------------------------------- 1 | package com.venmo.android.pin.pindemo.demo; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.app.Activity; 6 | import android.view.View; 7 | 8 | public class PinLauncher extends Activity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_pin_launcher); 14 | 15 | final Intent launchIntent = new Intent(); 16 | 17 | findViewById(R.id.sdk_button).setOnClickListener(new View.OnClickListener() { 18 | @Override 19 | public void onClick(View v) { 20 | launchIntent.setClass(PinLauncher.this, PinDemoActivity.class); 21 | 22 | startActivity(launchIntent); 23 | } 24 | }); 25 | 26 | findViewById(R.id.support_button).setOnClickListener(new View.OnClickListener() { 27 | @Override 28 | public void onClick(View v) { 29 | launchIntent.setClass(PinLauncher.this, PinSupportDemoActivity.class); 30 | 31 | startActivity(launchIntent); 32 | } 33 | }); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /demo/src/main/java/com/venmo/android/pin/pindemo/demo/PinSupportDemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.venmo.android.pin.pindemo.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentActivity; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | import android.widget.Toast; 9 | 10 | import com.venmo.android.pin.PinListener; 11 | import com.venmo.android.pin.PinSupportFragment; 12 | import com.venmo.android.pin.util.PinHelper; 13 | 14 | public class PinSupportDemoActivity extends FragmentActivity implements PinListener { 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_pin_demo); 20 | Fragment toShow = PinHelper.hasDefaultPinSaved(this) ? 21 | PinSupportFragment.newInstanceForVerification() : 22 | PinSupportFragment.newInstanceForCreation(); 23 | 24 | getSupportFragmentManager().beginTransaction() 25 | .replace(R.id.root, toShow, toShow.getClass().getSimpleName()) 26 | .commit(); 27 | } 28 | 29 | @Override 30 | public boolean onCreateOptionsMenu(Menu menu) { 31 | getMenuInflater().inflate(R.menu.pin_demo, menu); 32 | return true; 33 | } 34 | 35 | @Override 36 | public boolean onOptionsItemSelected(MenuItem item) { 37 | int id = item.getItemId(); 38 | if (id == R.id.action_settings) { 39 | PinHelper.resetDefaultSavedPin(this); 40 | recreate(); 41 | return true; 42 | } 43 | return super.onOptionsItemSelected(item); 44 | } 45 | 46 | @Override 47 | public void onValidated() { 48 | Toast.makeText(this, "Validated PIN!", Toast.LENGTH_SHORT).show(); 49 | recreate(); 50 | } 51 | 52 | @Override 53 | public void onPinCreated() { 54 | Toast.makeText(this, "Created PIN!", Toast.LENGTH_SHORT).show(); 55 | recreate(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/venmo/android-pin/ad8137d6d5510a6f01e9ed4a72501dec598a1a64/demo/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/venmo/android-pin/ad8137d6d5510a6f01e9ed4a72501dec598a1a64/demo/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_pin_demo.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_pin_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 |