├── LICENSE ├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── andrognito │ │ └── patternlockdemo │ │ └── ExampleInstrumentedTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── andrognito │ │ └── patternlockdemo │ │ └── MainActivity.java │ └── res │ ├── drawable │ ├── img_no_avatar.png │ └── logo.png │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── patternlockview-reactive ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── andrognito │ │ └── rxpatternlockview │ │ ├── RxPatternLockView.java │ │ ├── events │ │ ├── BasePatternLockEvent.java │ │ ├── PatternLockCompleteEvent.java │ │ ├── PatternLockCompoundEvent.java │ │ └── PatternLockProgressEvent.java │ │ ├── observables │ │ ├── BasePatternLockViewObservable.java │ │ ├── PatternLockViewCompleteObservable.java │ │ ├── PatternLockViewCompoundObservable.java │ │ └── PatternLockViewProgressObservable.java │ │ └── utils │ │ └── Preconditions.java │ └── res │ └── values │ └── strings.xml ├── patternlockview ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── andrognito │ │ └── patternlockview │ │ ├── PatternLockView.java │ │ ├── listener │ │ └── PatternLockViewListener.java │ │ └── utils │ │ ├── PatternLockUtils.java │ │ ├── RandomUtils.java │ │ └── ResourceUtils.java │ └── res │ └── values │ ├── attrs.xml │ ├── color.xml │ ├── dimens.xml │ └── strings.xml ├── screenshots ├── logo.png ├── pattern-lock-view-banner.png ├── pattern_lock_view_2_small.gif └── pattern_lock_view_small.gif └── settings.gradle /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![PatternLockView](https://github.com/aritraroy/PatternLockView/blob/master/screenshots/pattern-lock-view-banner.png?raw=true) 2 | 3 | # PatternLockView 4 | An easy-to-use, customizable, Material Design ready Pattern Lock view for Android. 5 | 6 | ### Specs 7 | [ ![Download](https://api.bintray.com/packages/aritraroy/maven/patternlockview/images/download.svg) ](https://bintray.com/aritraroy/maven/patternlockview/_latestVersion) [![API](https://img.shields.io/badge/API-14%2B-orange.svg?style=flat)](https://android-arsenal.com/api?level=14) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-PatternLockView-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/5515) 8 | 9 | 10 | This library allows you to implement pattern locking mechanism in your app **easily and quickly**. It is very easy to use and there are **plenty of customization options** available to change the functionality and look-and-feel of this view to match your needs. 11 | 12 | It also **supports RxJava 2 view bindings**, so if you are a fan of reactive programming (just like me), you can get a stream of updates as the user draws the pattern. 13 | 14 | ![PatternLockView](https://github.com/aritraroy/PatternLockView/blob/master/screenshots/pattern_lock_view_small.gif?raw=true) ![PatternLockView](https://github.com/aritraroy/PatternLockView/blob/master/screenshots/pattern_lock_view_2_small.gif?raw=true) 15 | 16 | 17 | # Download 18 | 19 | This library is available in **jCenter** which is the default Maven repository used in Android Studio. 20 | 21 | ## Gradle 22 | ```gradle 23 | dependencies { 24 | // other dependencies here 25 | 26 | compile 'com.andrognito.patternlockview:patternlockview:1.0.0' 27 | // Optional, for RxJava2 adapter 28 | compile 'com.andrognito.patternlockview:patternlockview-reactive:1.0.0' 29 | } 30 | ``` 31 | 32 | ### Spread Some :heart: 33 | [![GitHub stars](https://img.shields.io/github/stars/aritraroy/PatternLockView.svg?style=social&label=Star)](https://github.com/aritraroy) [![GitHub followers](https://img.shields.io/github/followers/aritraroy.svg?style=social&label=Follow)](https://github.com/aritraroy) 34 | [![Twitter Follow](https://img.shields.io/twitter/follow/aritraroy.svg?style=social)](https://twitter.com/aritraroy) 35 | 36 | 37 | # Usage 38 | We recommend you to check the [sample project](https://github.com/aritraroy/PatternLockView/blob/master/app/src/main/java/com/andrognito/patternlockdemo/MainActivity.java) to get a complete understanding of the library. The step-by-step implementation guide is as follows. 39 | 40 | ### Step 1 41 | 42 | Place the view in your XML layout file. 43 | 44 | ```xml 45 | 49 | ``` 50 | 51 | This is enough to get the view rendered in your layout. But you would certainly want to add a callback listener to listen to pattern changes. 52 | 53 | ### Step 2 54 | 55 | Reference the view in code and add a listener to it. 56 | 57 | ```java 58 | mPatternLockView = (PatternLockView) findViewById(R.id.pattern_lock_view); 59 | mPatternLockView.addPatternLockListener(mPatternLockViewListener); 60 | ``` 61 | 62 | Implement the listener interface as follows, 63 | 64 | ```java 65 | private PatternLockViewListener mPatternLockViewListener = new PatternLockViewListener() { 66 | @Override 67 | public void onStarted() { 68 | Log.d(getClass().getName(), "Pattern drawing started"); 69 | } 70 | 71 | @Override 72 | public void onProgress(List progressPattern) { 73 | Log.d(getClass().getName(), "Pattern progress: " + 74 | PatternLockUtils.patternToString(mPatternLockView, progressPattern)); 75 | } 76 | 77 | @Override 78 | public void onComplete(List pattern) { 79 | Log.d(getClass().getName(), "Pattern complete: " + 80 | PatternLockUtils.patternToString(mPatternLockView, pattern)); 81 | } 82 | 83 | @Override 84 | public void onCleared() { 85 | Log.d(getClass().getName(), "Pattern has been cleared"); 86 | } 87 | }; 88 | ``` 89 | 90 | And that's it! Your PatternLockView is ready to rock. You might also want to remove the listeners when not needed, `removePatternLockListener(mPatternLockViewListener);` 91 | 92 | 93 | ### Step 3 (Optional: ReactiveX Interface) 94 | 95 | For the RxJava fanboys, this library supports RxJava 2 view bindings. You can subscribe to this view to get a stream of pattern change updates. 96 | 97 | ```java 98 | RxPatternLockView.patternChanges(mPatternLockView) 99 | .subscribe(new Consumer() { 100 | @Override 101 | public void accept(PatternLockCompoundEvent event) throws Exception { 102 | if (event.getEventType() == PatternLockCompoundEvent.EventType.PATTERN_STARTED) { 103 | Log.d(getClass().getName(), "Pattern drawing started"); 104 | } else if (event.getEventType() == PatternLockCompoundEvent.EventType.PATTERN_PROGRESS) { 105 | Log.d(getClass().getName(), "Pattern progress: " + 106 | PatternLockUtils.patternToString(mPatternLockView, event.getPattern())); 107 | } else if (event.getEventType() == PatternLockCompoundEvent.EventType.PATTERN_COMPLETE) { 108 | Log.d(getClass().getName(), "Pattern complete: " + 109 | PatternLockUtils.patternToString(mPatternLockView, event.getPattern())); 110 | } else if (event.getEventType() == PatternLockCompoundEvent.EventType.PATTERN_CLEARED) { 111 | Log.d(getClass().getName(), "Pattern has been cleared"); 112 | } 113 | } 114 | }); 115 | ``` 116 | 117 | If you are not interested in getting the compound event, you should subscribe to `patternComplete()` and/or `patternProgress()` for the specific updates. Have a detailed look [here](https://github.com/aritraroy/PatternLockView/blob/master/patternlockview-rxadapter/src/main/java/com/andrognito/rxpatternlockview/RxPatternLockView.java). 118 | 119 | # Customization 120 | 121 | There are several customization options available which you can use to completely change the look-and-feel and functionality of this view to match your needs. 122 | 123 | ### XML (Quick and Easy) 124 | 125 | You can add various attributes to the PatternLockView from your XML layout. 126 | 127 | ```xml 128 | app:dotCount="3" // Change the no.of dots in a row (or column) 129 | app:dotNormalSize="12dp" // Change the size of the dots in normal state 130 | app:dotSelectedSize="24dp" // Change the size of the dots in selected state 131 | app:pathWidth="4dp" // Change the width of the path 132 | app:aspectRatioEnabled="true" // Set if the view should respect custom aspect ratio 133 | app:aspectRatio="square" // Set between "square", "width_bias", "height_bias" 134 | app:normalStateColor="@color/white" // Set the color of the pattern view in normal state 135 | app:correctStateColor="@color/primary" // Set the color of the pattern view in correct state 136 | app:wrongStateColor="@color/pomegranate" // Set the color of the pattern view in error state 137 | app:dotAnimationDuration="200" // Change the duration of the animating dots 138 | app:pathEndAnimationDuration="100" // Change the duration of the path end animaiton 139 | ``` 140 | 141 | ### JAVA (Programatically) 142 | 143 | You can also programatically change the properties of the view, thereby having more control over it. 144 | 145 | ```java 146 | mPatternLockView.setViewMode(PatternLockView.PatternViewMode.CORRECT); // Set the current viee more 147 | mPatternLockView.setInStealthMode(true); // Set the pattern in stealth mode (pattern drawing is hidden) 148 | mPatternLockView.setTactileFeedbackEnabled(true); // Enables vibration feedback when the pattern is drawn 149 | mPatternLockView.setInputEnabled(false); // Disables any input from the pattern lock view completely 150 | 151 | mPatternLockView.setDotCount(3); 152 | mPatternLockView.setDotNormalSize((int) ResourceUtils.getDimensionInPx(this, R.dimen.pattern_lock_dot_size)); 153 | mPatternLockView.setDotSelectedSize((int) ResourceUtils.getDimensionInPx(this, R.dimen.pattern_lock_dot_selected_size)); 154 | mPatternLockView.setPathWidth((int) ResourceUtils.getDimensionInPx(this, R.dimen.pattern_lock_path_width)); 155 | mPatternLockView.setAspectRatioEnabled(true); 156 | mPatternLockView.setAspectRatio(PatternLockView.AspectRatio.ASPECT_RATIO_HEIGHT_BIAS); 157 | mPatternLockView.setNormalStateColor(ResourceUtils.getColor(this, R.color.white)); 158 | mPatternLockView.setCorrectStateColor(ResourceUtils.getColor(this, R.color.primary)); 159 | mPatternLockView.setWrongStateColor(ResourceUtils.getColor(this, R.color.pomegranate)); 160 | mPatternLockView.setDotAnimationDuration(150); 161 | mPatternLockView.setPathEndAnimationDuration(100); 162 | 163 | ``` 164 | 165 | # Contribution 166 | 167 | This library is inspired from AOSP's [LockPatternView](https://github.com/android/platform_frameworks_base/blob/master/core/java/com/android/internal/widget/LockPatternView.java). There are lots of improvements and customization options added so that you can get started without any hassle. If you find a bug or would like to improve any aspect of it, feel free to contribute with pull requests. 168 | 169 | 170 | # About The Author 171 | 172 | ### Aritra Roy 173 | 174 | Android & Backend Developer. Blogger. Designer. Fitness Enthusiast. 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | # License 183 | 184 | ``` 185 | Copyright 2017 aritraroy 186 | 187 | Licensed under the Apache License, Version 2.0 (the "License"); 188 | you may not use this file except in compliance with the License. 189 | You may obtain a copy of the License at 190 | 191 | http://www.apache.org/licenses/LICENSE-2.0 192 | 193 | Unless required by applicable law or agreed to in writing, software 194 | distributed under the License is distributed on an "AS IS" BASIS, 195 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 196 | See the License for the specific language governing permissions and 197 | limitations under the License. 198 | ``` 199 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | 7 | defaultConfig { 8 | minSdkVersion rootProject.ext.minSdkVersion 9 | targetSdkVersion rootProject.ext.targetSdkVersion 10 | versionCode Integer.parseInt(project.VERSION_CODE) 11 | versionName project.VERSION_NAME 12 | } 13 | } 14 | 15 | dependencies { 16 | compile project(':patternlockview') 17 | compile project(':patternlockview-reactive') 18 | compile rootProject.ext.supportV7 19 | compile rootProject.ext.rxJava 20 | } 21 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/aritraroy/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/andrognito/patternlockdemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.patternlockdemo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.andrognito.patternlockdemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/andrognito/patternlockdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.patternlockdemo; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.util.Log; 6 | import android.view.Window; 7 | import android.view.WindowManager; 8 | 9 | import com.andrognito.patternlockview.PatternLockView; 10 | import com.andrognito.patternlockview.listener.PatternLockViewListener; 11 | import com.andrognito.patternlockview.utils.PatternLockUtils; 12 | import com.andrognito.patternlockview.utils.ResourceUtils; 13 | import com.andrognito.rxpatternlockview.RxPatternLockView; 14 | import com.andrognito.rxpatternlockview.events.PatternLockCompleteEvent; 15 | import com.andrognito.rxpatternlockview.events.PatternLockCompoundEvent; 16 | 17 | import java.util.List; 18 | 19 | import io.reactivex.functions.Consumer; 20 | 21 | public class MainActivity extends AppCompatActivity { 22 | 23 | private PatternLockView mPatternLockView; 24 | 25 | private PatternLockViewListener mPatternLockViewListener = new PatternLockViewListener() { 26 | @Override 27 | public void onStarted() { 28 | Log.d(getClass().getName(), "Pattern drawing started"); 29 | } 30 | 31 | @Override 32 | public void onProgress(List progressPattern) { 33 | Log.d(getClass().getName(), "Pattern progress: " + 34 | PatternLockUtils.patternToString(mPatternLockView, progressPattern)); 35 | } 36 | 37 | @Override 38 | public void onComplete(List pattern) { 39 | Log.d(getClass().getName(), "Pattern complete: " + 40 | PatternLockUtils.patternToString(mPatternLockView, pattern)); 41 | } 42 | 43 | @Override 44 | public void onCleared() { 45 | Log.d(getClass().getName(), "Pattern has been cleared"); 46 | } 47 | }; 48 | 49 | @Override 50 | protected void onCreate(Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | requestWindowFeature(Window.FEATURE_NO_TITLE); 53 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 54 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 55 | setContentView(R.layout.activity_main); 56 | 57 | mPatternLockView = (PatternLockView) findViewById(R.id.patter_lock_view); 58 | mPatternLockView.setDotCount(3); 59 | mPatternLockView.setDotNormalSize((int) ResourceUtils.getDimensionInPx(this, R.dimen.pattern_lock_dot_size)); 60 | mPatternLockView.setDotSelectedSize((int) ResourceUtils.getDimensionInPx(this, R.dimen.pattern_lock_dot_selected_size)); 61 | mPatternLockView.setPathWidth((int) ResourceUtils.getDimensionInPx(this, R.dimen.pattern_lock_path_width)); 62 | mPatternLockView.setAspectRatioEnabled(true); 63 | mPatternLockView.setAspectRatio(PatternLockView.AspectRatio.ASPECT_RATIO_HEIGHT_BIAS); 64 | mPatternLockView.setViewMode(PatternLockView.PatternViewMode.CORRECT); 65 | mPatternLockView.setDotAnimationDuration(150); 66 | mPatternLockView.setPathEndAnimationDuration(100); 67 | mPatternLockView.setCorrectStateColor(ResourceUtils.getColor(this, R.color.white)); 68 | mPatternLockView.setInStealthMode(false); 69 | mPatternLockView.setTactileFeedbackEnabled(true); 70 | mPatternLockView.setInputEnabled(true); 71 | mPatternLockView.addPatternLockListener(mPatternLockViewListener); 72 | 73 | RxPatternLockView.patternComplete(mPatternLockView) 74 | .subscribe(new Consumer() { 75 | @Override 76 | public void accept(PatternLockCompleteEvent patternLockCompleteEvent) throws Exception { 77 | Log.d(getClass().getName(), "Complete: " + patternLockCompleteEvent.getPattern().toString()); 78 | } 79 | }); 80 | 81 | RxPatternLockView.patternChanges(mPatternLockView) 82 | .subscribe(new Consumer() { 83 | @Override 84 | public void accept(PatternLockCompoundEvent event) throws Exception { 85 | if (event.getEventType() == PatternLockCompoundEvent.EventType.PATTERN_STARTED) { 86 | Log.d(getClass().getName(), "Pattern drawing started"); 87 | } else if (event.getEventType() == PatternLockCompoundEvent.EventType.PATTERN_PROGRESS) { 88 | Log.d(getClass().getName(), "Pattern progress: " + 89 | PatternLockUtils.patternToString(mPatternLockView, event.getPattern())); 90 | } else if (event.getEventType() == PatternLockCompoundEvent.EventType.PATTERN_COMPLETE) { 91 | Log.d(getClass().getName(), "Pattern complete: " + 92 | PatternLockUtils.patternToString(mPatternLockView, event.getPattern())); 93 | } else if (event.getEventType() == PatternLockCompoundEvent.EventType.PATTERN_CLEARED) { 94 | Log.d(getClass().getName(), "Pattern has been cleared"); 95 | } 96 | } 97 | }); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_no_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/app/src/main/res/drawable/img_no_avatar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/app/src/main/res/drawable/logo.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 30 | 31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #303030 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PatternLockDemo 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.0' 9 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | jcenter() 17 | } 18 | } 19 | 20 | ext { 21 | minSdkVersion = 14 22 | targetSdkVersion = 25 23 | compileSdkVersion = 25 24 | buildToolsVersion = '25.0.2' 25 | 26 | // dependencies 27 | supportV7 = 'com.android.support:appcompat-v7:25.3.0' 28 | rxJava = 'io.reactivex.rxjava2:rxjava:2.0.2' 29 | rxAndroid = 'io.reactivex.rxjava2:rxandroid:2.0.0' 30 | } 31 | 32 | task clean(type: Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=1.0.0 2 | VERSION_CODE=1 3 | GROUP=com.andrognito.patternlockview 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Mar 19 20:13:42 IST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /patternlockview-reactive/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | ext { 4 | bintrayRepo = 'maven' 5 | bintrayName = 'patternlockview-reactive' 6 | 7 | publishedGroupId = 'com.andrognito.patternlockview' 8 | libraryName = 'patternlockview-reactive' 9 | artifact = 'patternlockview-reactive' 10 | 11 | libraryDescription = 'An easy-to-use, customizable, Material Design complaint Pattern Lock ' + 12 | 'view for Android' 13 | 14 | siteUrl = 'https://github.com/aritraroy/PatternLockView' 15 | gitUrl = 'https://github.com/aritraroy/PatternLockView.git' 16 | 17 | libraryVersion = '1.0.0' 18 | 19 | developerId = 'aritraroy' 20 | developerName = 'Aritra Roy' 21 | developerEmail = 'aritra.roy.in@gmail.com' 22 | 23 | licenseName = 'The Apache Software License, Version 2.0' 24 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 25 | allLicenses = ["Apache-2.0"] 26 | } 27 | 28 | android { 29 | compileSdkVersion rootProject.ext.compileSdkVersion 30 | buildToolsVersion rootProject.ext.buildToolsVersion 31 | 32 | defaultConfig { 33 | minSdkVersion rootProject.ext.minSdkVersion 34 | targetSdkVersion rootProject.ext.targetSdkVersion 35 | versionCode Integer.parseInt(project.VERSION_CODE) 36 | versionName project.VERSION_NAME 37 | } 38 | } 39 | 40 | dependencies { 41 | compile project(':patternlockview') 42 | compile rootProject.ext.rxJava 43 | compile rootProject.ext.rxAndroid 44 | } 45 | 46 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' 47 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' 48 | 49 | -------------------------------------------------------------------------------- /patternlockview-reactive/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/aritraroy/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /patternlockview-reactive/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /patternlockview-reactive/src/main/java/com/andrognito/rxpatternlockview/RxPatternLockView.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.rxpatternlockview; 2 | 3 | import com.andrognito.patternlockview.PatternLockView; 4 | import com.andrognito.rxpatternlockview.events.PatternLockCompleteEvent; 5 | import com.andrognito.rxpatternlockview.events.PatternLockCompoundEvent; 6 | import com.andrognito.rxpatternlockview.events.PatternLockProgressEvent; 7 | import com.andrognito.rxpatternlockview.observables.PatternLockViewCompleteObservable; 8 | import com.andrognito.rxpatternlockview.observables.PatternLockViewCompoundObservable; 9 | import com.andrognito.rxpatternlockview.observables.PatternLockViewProgressObservable; 10 | 11 | import io.reactivex.Observable; 12 | 13 | import static com.andrognito.rxpatternlockview.utils.Preconditions.checkNotNull; 14 | 15 | /** 16 | * Created by aritraroy on 27/03/17. 17 | */ 18 | 19 | public class RxPatternLockView { 20 | 21 | /** 22 | * Create an observable for all events of this {@code view}. 23 | *

24 | * Warning: The created observable keeps a strong reference to {@code view}. 25 | * Unsubscribe to free this reference. 26 | */ 27 | public static Observable patternChanges(PatternLockView patternLockView) { 28 | checkNotNull(patternLockView, "view == null"); 29 | return new PatternLockViewCompoundObservable(patternLockView, false); 30 | } 31 | 32 | /** 33 | * Create an observable for all events of this {@code view}. 34 | *

35 | * Warning: The created observable keeps a strong reference to {@code view}. 36 | * Unsubscribe to free this reference. 37 | *

38 | * Note: A value will be emitted immediately on subscribe. 39 | */ 40 | public static Observable patternChanges(PatternLockView patternLockView, 41 | boolean emitInitialValue) { 42 | checkNotNull(patternLockView, "view == null"); 43 | return new PatternLockViewCompoundObservable(patternLockView, emitInitialValue); 44 | } 45 | 46 | /** 47 | * Create an observable for only the pattern complete event of this {@code view}. 48 | *

49 | * Warning: The created observable keeps a strong reference to {@code view}. 50 | * Unsubscribe to free this reference. 51 | */ 52 | public static Observable patternComplete(PatternLockView patternLockView) { 53 | checkNotNull(patternLockView, "view == null"); 54 | return new PatternLockViewCompleteObservable(patternLockView, false); 55 | } 56 | 57 | /** 58 | * Create an observable for only the pattern complete event of this {@code view}. 59 | *

60 | * Warning: The created observable keeps a strong reference to {@code view}. 61 | * Unsubscribe to free this reference. 62 | *

63 | * Note: A value will be emitted immediately on subscribe. 64 | */ 65 | public static Observable patternComplete(PatternLockView patternLockView, 66 | boolean emitInitialValues) { 67 | checkNotNull(patternLockView, "view == null"); 68 | return new PatternLockViewCompleteObservable(patternLockView, emitInitialValues); 69 | } 70 | 71 | /** 72 | * Create an observable for only the pattern progress event of this {@code view}. 73 | *

74 | * Warning: The created observable keeps a strong reference to {@code view}. 75 | * Unsubscribe to free this reference. 76 | */ 77 | public static Observable patternProgress(PatternLockView patternLockView) { 78 | checkNotNull(patternLockView, "view == null"); 79 | return new PatternLockViewProgressObservable(patternLockView, false); 80 | } 81 | 82 | /** 83 | * Create an observable for only the pattern progress event of this {@code view}. 84 | *

85 | * Warning: The created observable keeps a strong reference to {@code view}. 86 | * Unsubscribe to free this reference. 87 | *

88 | * Note: A value will be emitted immediately on subscribe. 89 | */ 90 | public static Observable patternProgress(PatternLockView patternLockView, 91 | boolean emitInitialValues) { 92 | checkNotNull(patternLockView, "view == null"); 93 | return new PatternLockViewProgressObservable(patternLockView, emitInitialValues); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /patternlockview-reactive/src/main/java/com/andrognito/rxpatternlockview/events/BasePatternLockEvent.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.rxpatternlockview.events; 2 | 3 | import android.support.annotation.Nullable; 4 | 5 | import com.andrognito.patternlockview.PatternLockView; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Created by aritraroy on 01/04/17. 12 | */ 13 | 14 | public abstract class BasePatternLockEvent { 15 | protected List mPattern; 16 | 17 | protected BasePatternLockEvent(List pattern) { 18 | mPattern = pattern; 19 | } 20 | 21 | @Nullable 22 | public List getPattern() { 23 | if (mPattern == null) { 24 | return new ArrayList<>(); 25 | } 26 | return new ArrayList<>(mPattern); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /patternlockview-reactive/src/main/java/com/andrognito/rxpatternlockview/events/PatternLockCompleteEvent.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.rxpatternlockview.events; 2 | 3 | import com.andrognito.patternlockview.PatternLockView; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by aritraroy on 01/04/17. 9 | */ 10 | 11 | public class PatternLockCompleteEvent extends BasePatternLockEvent { 12 | 13 | public PatternLockCompleteEvent(List pattern) { 14 | super(pattern); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /patternlockview-reactive/src/main/java/com/andrognito/rxpatternlockview/events/PatternLockCompoundEvent.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.rxpatternlockview.events; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import com.andrognito.patternlockview.PatternLockView; 6 | 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.util.List; 10 | 11 | import static com.andrognito.rxpatternlockview.events.PatternLockCompoundEvent.EventType.PATTERN_CLEARED; 12 | import static com.andrognito.rxpatternlockview.events.PatternLockCompoundEvent.EventType.PATTERN_COMPLETE; 13 | import static com.andrognito.rxpatternlockview.events.PatternLockCompoundEvent.EventType.PATTERN_PROGRESS; 14 | import static com.andrognito.rxpatternlockview.events.PatternLockCompoundEvent.EventType.PATTERN_STARTED; 15 | 16 | /** 17 | * Created by aritraroy on 27/03/17. 18 | */ 19 | 20 | public final class PatternLockCompoundEvent extends BasePatternLockEvent { 21 | 22 | @IntDef({PATTERN_STARTED, PATTERN_PROGRESS, PATTERN_COMPLETE, PATTERN_CLEARED}) 23 | @Retention(RetentionPolicy.SOURCE) 24 | public @interface EventType { 25 | int PATTERN_STARTED = 0; 26 | int PATTERN_PROGRESS = 1; 27 | int PATTERN_COMPLETE = 2; 28 | int PATTERN_CLEARED = 3; 29 | } 30 | 31 | private final int mEventType; 32 | 33 | public PatternLockCompoundEvent(@EventType int eventType, List pattern) { 34 | super(pattern); 35 | mEventType = eventType; 36 | } 37 | 38 | @EventType 39 | public int getEventType() { 40 | return mEventType; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /patternlockview-reactive/src/main/java/com/andrognito/rxpatternlockview/events/PatternLockProgressEvent.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.rxpatternlockview.events; 2 | 3 | import com.andrognito.patternlockview.PatternLockView; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by aritraroy on 01/04/17. 9 | */ 10 | 11 | public class PatternLockProgressEvent extends BasePatternLockEvent { 12 | 13 | public PatternLockProgressEvent(List pattern) { 14 | super(pattern); 15 | } 16 | } -------------------------------------------------------------------------------- /patternlockview-reactive/src/main/java/com/andrognito/rxpatternlockview/observables/BasePatternLockViewObservable.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.rxpatternlockview.observables; 2 | 3 | import com.andrognito.patternlockview.PatternLockView; 4 | 5 | import io.reactivex.Observable; 6 | import io.reactivex.Observer; 7 | 8 | /** 9 | * Created by aritraroy on 01/04/17. 10 | */ 11 | 12 | public abstract class BasePatternLockViewObservable 13 | extends Observable { 14 | protected PatternLockView mPatternLockView; 15 | protected boolean mEmitInitialValue; 16 | 17 | protected BasePatternLockViewObservable(PatternLockView patternLockView, boolean emitInitialValue) { 18 | mPatternLockView = patternLockView; 19 | mEmitInitialValue = emitInitialValue; 20 | } 21 | 22 | protected abstract void subscribeListener(Observer observer); 23 | } 24 | -------------------------------------------------------------------------------- /patternlockview-reactive/src/main/java/com/andrognito/rxpatternlockview/observables/PatternLockViewCompleteObservable.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.rxpatternlockview.observables; 2 | 3 | import com.andrognito.patternlockview.PatternLockView; 4 | import com.andrognito.patternlockview.listener.PatternLockViewListener; 5 | import com.andrognito.rxpatternlockview.events.PatternLockCompleteEvent; 6 | 7 | import java.util.List; 8 | 9 | import io.reactivex.Observer; 10 | import io.reactivex.android.MainThreadDisposable; 11 | 12 | /** 13 | * Created by aritraroy on 01/04/17. 14 | */ 15 | 16 | public class PatternLockViewCompleteObservable extends 17 | BasePatternLockViewObservable { 18 | 19 | public PatternLockViewCompleteObservable(PatternLockView patternLockView, boolean emitInitialValues) { 20 | super(patternLockView, emitInitialValues); 21 | } 22 | 23 | @Override 24 | protected void subscribeListener(Observer observer) { 25 | InternalListener internalListener = new InternalListener(mPatternLockView, observer); 26 | observer.onSubscribe(internalListener); 27 | mPatternLockView.addPatternLockListener(internalListener); 28 | } 29 | 30 | @Override 31 | protected void subscribeActual(Observer observer) { 32 | subscribeListener(observer); 33 | if (mEmitInitialValue) { 34 | observer.onNext(new PatternLockCompleteEvent(mPatternLockView.getPattern())); 35 | } 36 | } 37 | 38 | private static final class InternalListener extends MainThreadDisposable 39 | implements PatternLockViewListener { 40 | private final PatternLockView view; 41 | private final Observer observer; 42 | 43 | InternalListener(PatternLockView view, Observer observer) { 44 | this.view = view; 45 | this.observer = observer; 46 | } 47 | 48 | @Override 49 | public void onStarted() { 50 | 51 | } 52 | 53 | @Override 54 | public void onProgress(List progressPattern) { 55 | 56 | } 57 | 58 | @Override 59 | public void onComplete(List pattern) { 60 | if (!isDisposed()) { 61 | observer.onNext(new PatternLockCompleteEvent(pattern)); 62 | } 63 | } 64 | 65 | @Override 66 | public void onCleared() { 67 | 68 | } 69 | 70 | @Override 71 | protected void onDispose() { 72 | view.removePatternLockListener(this); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /patternlockview-reactive/src/main/java/com/andrognito/rxpatternlockview/observables/PatternLockViewCompoundObservable.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.rxpatternlockview.observables; 2 | 3 | import com.andrognito.patternlockview.PatternLockView; 4 | import com.andrognito.patternlockview.listener.PatternLockViewListener; 5 | import com.andrognito.rxpatternlockview.events.PatternLockCompoundEvent; 6 | 7 | import java.util.List; 8 | 9 | import io.reactivex.Observer; 10 | import io.reactivex.android.MainThreadDisposable; 11 | 12 | 13 | /** 14 | * Created by aritraroy on 27/03/17. 15 | */ 16 | 17 | public class PatternLockViewCompoundObservable 18 | extends BasePatternLockViewObservable { 19 | 20 | public PatternLockViewCompoundObservable(PatternLockView patternLockView, boolean emitInitialValue) { 21 | super(patternLockView, emitInitialValue); 22 | } 23 | 24 | @Override 25 | protected void subscribeActual(Observer observer) { 26 | subscribeListener(observer); 27 | if (mEmitInitialValue) { 28 | observer.onNext(new PatternLockCompoundEvent(PatternLockCompoundEvent.EventType.PATTERN_STARTED, 29 | mPatternLockView.getPattern())); 30 | } 31 | } 32 | 33 | @Override 34 | protected void subscribeListener(Observer observer) { 35 | InternalListener internalListener = new InternalListener(mPatternLockView, observer); 36 | observer.onSubscribe(internalListener); 37 | mPatternLockView.addPatternLockListener(internalListener); 38 | } 39 | 40 | private static final class InternalListener extends MainThreadDisposable 41 | implements PatternLockViewListener { 42 | private final PatternLockView view; 43 | private final Observer observer; 44 | 45 | InternalListener(PatternLockView view, Observer 46 | observer) { 47 | this.view = view; 48 | this.observer = observer; 49 | } 50 | 51 | @Override 52 | public void onStarted() { 53 | if (!isDisposed()) { 54 | observer.onNext(new PatternLockCompoundEvent(PatternLockCompoundEvent 55 | .EventType.PATTERN_STARTED, null)); 56 | } 57 | } 58 | 59 | @Override 60 | public void onProgress(List progressPattern) { 61 | if (!isDisposed()) { 62 | observer.onNext(new PatternLockCompoundEvent(PatternLockCompoundEvent 63 | .EventType.PATTERN_PROGRESS, progressPattern)); 64 | } 65 | } 66 | 67 | @Override 68 | public void onComplete(List pattern) { 69 | if (!isDisposed()) { 70 | observer.onNext(new PatternLockCompoundEvent(PatternLockCompoundEvent 71 | .EventType.PATTERN_COMPLETE, pattern)); 72 | } 73 | } 74 | 75 | @Override 76 | public void onCleared() { 77 | if (!isDisposed()) { 78 | observer.onNext(new PatternLockCompoundEvent(PatternLockCompoundEvent 79 | .EventType.PATTERN_CLEARED, null)); 80 | } 81 | } 82 | 83 | @Override 84 | protected void onDispose() { 85 | view.removePatternLockListener(this); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /patternlockview-reactive/src/main/java/com/andrognito/rxpatternlockview/observables/PatternLockViewProgressObservable.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.rxpatternlockview.observables; 2 | 3 | import com.andrognito.patternlockview.PatternLockView; 4 | import com.andrognito.patternlockview.listener.PatternLockViewListener; 5 | import com.andrognito.rxpatternlockview.events.PatternLockProgressEvent; 6 | 7 | import java.util.List; 8 | 9 | import io.reactivex.Observer; 10 | import io.reactivex.android.MainThreadDisposable; 11 | 12 | /** 13 | * Created by aritraroy on 01/04/17. 14 | */ 15 | 16 | public class PatternLockViewProgressObservable extends 17 | BasePatternLockViewObservable { 18 | 19 | public PatternLockViewProgressObservable(PatternLockView patternLockView, boolean emitInitialValue) { 20 | super(patternLockView, emitInitialValue); 21 | } 22 | 23 | @Override 24 | protected void subscribeListener(Observer observer) { 25 | InternalListener internalListener = new InternalListener(mPatternLockView, observer); 26 | observer.onSubscribe(internalListener); 27 | mPatternLockView.addPatternLockListener(internalListener); 28 | } 29 | 30 | @Override 31 | protected void subscribeActual(Observer observer) { 32 | subscribeListener(observer); 33 | if (mEmitInitialValue) { 34 | observer.onNext(new PatternLockProgressEvent(mPatternLockView.getPattern())); 35 | } 36 | } 37 | 38 | private static final class InternalListener extends MainThreadDisposable 39 | implements PatternLockViewListener { 40 | private final PatternLockView view; 41 | private final Observer observer; 42 | 43 | InternalListener(PatternLockView view, Observer observer) { 44 | this.view = view; 45 | this.observer = observer; 46 | } 47 | 48 | @Override 49 | public void onStarted() { 50 | 51 | } 52 | 53 | @Override 54 | public void onProgress(List progressPattern) { 55 | if (!isDisposed()) { 56 | observer.onNext(new PatternLockProgressEvent(progressPattern)); 57 | } 58 | } 59 | 60 | @Override 61 | public void onComplete(List pattern) { 62 | 63 | } 64 | 65 | @Override 66 | public void onCleared() { 67 | 68 | } 69 | 70 | @Override 71 | protected void onDispose() { 72 | view.removePatternLockListener(this); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /patternlockview-reactive/src/main/java/com/andrognito/rxpatternlockview/utils/Preconditions.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.rxpatternlockview.utils; 2 | 3 | import android.os.Looper; 4 | 5 | import io.reactivex.Observer; 6 | 7 | /** 8 | * Created by aritraroy on 02/04/17. 9 | */ 10 | 11 | public final class Preconditions { 12 | 13 | private Preconditions() { 14 | throw new AssertionError("You can not instantiate this class. Use its static utility " + 15 | "methods instead"); 16 | } 17 | 18 | public static void checkNotNull(Object value, String message) { 19 | if (value == null) { 20 | throw new NullPointerException(message); 21 | } 22 | } 23 | 24 | public static boolean checkMainThread(Observer observer) { 25 | if (Looper.myLooper() != Looper.getMainLooper()) { 26 | observer.onError(new IllegalStateException( 27 | "Expected to be called on the main thread but was " + Thread.currentThread().getName())); 28 | return false; 29 | } 30 | return true; 31 | } 32 | } -------------------------------------------------------------------------------- /patternlockview-reactive/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RxPatternLockView 3 | 4 | -------------------------------------------------------------------------------- /patternlockview/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | ext { 4 | bintrayRepo = 'maven' 5 | bintrayName = 'patternlockview' 6 | 7 | publishedGroupId = 'com.andrognito.patternlockview' 8 | libraryName = 'patternlockview' 9 | artifact = 'patternlockview' 10 | 11 | libraryDescription = 'An easy-to-use, customizable, Material Design complaint Pattern Lock ' + 12 | 'view for Android' 13 | 14 | siteUrl = 'https://github.com/aritraroy/PatternLockView' 15 | gitUrl = 'https://github.com/aritraroy/PatternLockView.git' 16 | 17 | libraryVersion = '1.0.0' 18 | 19 | developerId = 'aritraroy' 20 | developerName = 'Aritra Roy' 21 | developerEmail = 'aritra.roy.in@gmail.com' 22 | 23 | licenseName = 'The Apache Software License, Version 2.0' 24 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 25 | allLicenses = ["Apache-2.0"] 26 | } 27 | 28 | android { 29 | compileSdkVersion rootProject.ext.compileSdkVersion 30 | buildToolsVersion rootProject.ext.buildToolsVersion 31 | 32 | defaultConfig { 33 | minSdkVersion rootProject.ext.minSdkVersion 34 | targetSdkVersion rootProject.ext.targetSdkVersion 35 | versionCode Integer.parseInt(project.VERSION_CODE) 36 | versionName project.VERSION_NAME 37 | } 38 | } 39 | 40 | dependencies { 41 | compile rootProject.ext.supportV7 42 | } 43 | 44 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' 45 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' -------------------------------------------------------------------------------- /patternlockview/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/aritraroy/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /patternlockview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /patternlockview/src/main/java/com/andrognito/patternlockview/PatternLockView.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.patternlockview; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.content.res.TypedArray; 8 | import android.graphics.Canvas; 9 | import android.graphics.Paint; 10 | import android.graphics.Path; 11 | import android.graphics.Rect; 12 | import android.os.Build; 13 | import android.os.Debug; 14 | import android.os.Parcel; 15 | import android.os.Parcelable; 16 | import android.os.SystemClock; 17 | import android.support.annotation.ColorInt; 18 | import android.support.annotation.Dimension; 19 | import android.support.annotation.IntDef; 20 | import android.util.AttributeSet; 21 | import android.view.HapticFeedbackConstants; 22 | import android.view.MotionEvent; 23 | import android.view.View; 24 | import android.view.accessibility.AccessibilityEvent; 25 | import android.view.accessibility.AccessibilityManager; 26 | import android.view.animation.AnimationUtils; 27 | import android.view.animation.Interpolator; 28 | 29 | import com.andrognito.patternlockview.listener.PatternLockViewListener; 30 | import com.andrognito.patternlockview.utils.PatternLockUtils; 31 | import com.andrognito.patternlockview.utils.ResourceUtils; 32 | 33 | import java.lang.annotation.Retention; 34 | import java.lang.annotation.RetentionPolicy; 35 | import java.util.ArrayList; 36 | import java.util.List; 37 | 38 | import static com.andrognito.patternlockview.PatternLockView.AspectRatio.ASPECT_RATIO_HEIGHT_BIAS; 39 | import static com.andrognito.patternlockview.PatternLockView.AspectRatio.ASPECT_RATIO_SQUARE; 40 | import static com.andrognito.patternlockview.PatternLockView.AspectRatio.ASPECT_RATIO_WIDTH_BIAS; 41 | import static com.andrognito.patternlockview.PatternLockView.PatternViewMode.AUTO_DRAW; 42 | import static com.andrognito.patternlockview.PatternLockView.PatternViewMode.CORRECT; 43 | import static com.andrognito.patternlockview.PatternLockView.PatternViewMode.WRONG; 44 | 45 | /** 46 | * Displays a powerful, customizable and Material Design complaint pattern lock in the screen which 47 | * can be used to lock any Activity or Fragment from the user 48 | */ 49 | public class PatternLockView extends View { 50 | 51 | /** 52 | * Represents the aspect ratio for the View 53 | */ 54 | @IntDef({ASPECT_RATIO_SQUARE, ASPECT_RATIO_WIDTH_BIAS, ASPECT_RATIO_HEIGHT_BIAS}) 55 | @Retention(RetentionPolicy.SOURCE) 56 | public @interface AspectRatio { 57 | // Width and height will be same. Minimum of width and height 58 | int ASPECT_RATIO_SQUARE = 0; 59 | // Width will be fixed. The height will be the minimum of width and height 60 | int ASPECT_RATIO_WIDTH_BIAS = 1; 61 | // Height will be fixed. The width will be the minimum of width and height 62 | int ASPECT_RATIO_HEIGHT_BIAS = 2; 63 | } 64 | 65 | /** 66 | * Represents the different modes in which this view can be represented 67 | */ 68 | @IntDef({CORRECT, AUTO_DRAW, WRONG}) 69 | @Retention(RetentionPolicy.SOURCE) 70 | public @interface PatternViewMode { 71 | /** 72 | * This state represents a correctly drawn pattern by the user. The color of the path and 73 | * the dots both would be changed to this color. 74 | *

75 | * (NOTE - Consider showing this state in a friendly color) 76 | */ 77 | int CORRECT = 0; 78 | /** 79 | * Automatically draw the pattern for demo or tutorial purposes. 80 | */ 81 | int AUTO_DRAW = 1; 82 | /** 83 | * This state represents a wrongly drawn pattern by the user. The color of the path and 84 | * the dots both would be changed to this color. 85 | *

86 | * (NOTE - Consider showing this state in an attention-seeking color) 87 | */ 88 | int WRONG = 2; 89 | } 90 | 91 | private static final int DEFAULT_PATTERN_DOT_COUNT = 3; 92 | private static final boolean PROFILE_DRAWING = false; 93 | 94 | /** 95 | * The time (in millis) spend in animating each circle of a lock pattern if 96 | * the animating mode is set. The entire animation should take this constant 97 | * the length of the pattern to complete. 98 | */ 99 | private static final int MILLIS_PER_CIRCLE_ANIMATING = 700; 100 | 101 | // Amount of time (in millis) spent to animate a dot 102 | private static final int DEFAULT_DOT_ANIMATION_DURATION = 190; 103 | // Amount of time (in millis) spent to animate a path ends 104 | private static final int DEFAULT_PATH_END_ANIMATION_DURATION = 100; 105 | // This can be used to avoid updating the display for very small motions or noisy panels 106 | private static final float DEFAULT_DRAG_THRESHOLD = 0.0f; 107 | 108 | private DotState[][] mDotStates; 109 | private int mPatternSize; 110 | private boolean mDrawingProfilingStarted = false; 111 | private long mAnimatingPeriodStart; 112 | private float mHitFactor = 0.6f; 113 | 114 | // Made static so that the static inner class can use it 115 | private static int sDotCount; 116 | 117 | private boolean mAspectRatioEnabled; 118 | private int mAspectRatio; 119 | private int mNormalStateColor; 120 | private int mWrongStateColor; 121 | private int mCorrectStateColor; 122 | private int mPathWidth; 123 | private int mDotNormalSize; 124 | private int mDotSelectedSize; 125 | private int mDotAnimationDuration; 126 | private int mPathEndAnimationDuration; 127 | 128 | private Paint mDotPaint; 129 | private Paint mPathPaint; 130 | 131 | private List mPatternListeners; 132 | // The pattern represented as a list of connected {@link Dot} 133 | private ArrayList mPattern; 134 | 135 | /** 136 | * Lookup table for the dots of the pattern we are currently drawing. 137 | * This will be the dots of the complete pattern unless we are animating, 138 | * in which case we use this to hold the dots we are drawing for the in 139 | * progress animation. 140 | */ 141 | private boolean[][] mPatternDrawLookup; 142 | 143 | private float mInProgressX = -1; 144 | private float mInProgressY = -1; 145 | 146 | private int mPatternViewMode = CORRECT; 147 | private boolean mInputEnabled = true; 148 | private boolean mInStealthMode = false; 149 | private boolean mEnableHapticFeedback = true; 150 | private boolean mPatternInProgress = false; 151 | 152 | private float mViewWidth; 153 | private float mViewHeight; 154 | 155 | private final Path mCurrentPath = new Path(); 156 | private final Rect mInvalidate = new Rect(); 157 | private final Rect mTempInvalidateRect = new Rect(); 158 | 159 | private Interpolator mFastOutSlowInInterpolator; 160 | private Interpolator mLinearOutSlowInInterpolator; 161 | 162 | public PatternLockView(Context context) { 163 | this(context, null); 164 | } 165 | 166 | public PatternLockView(Context context, AttributeSet attrs) { 167 | super(context, attrs); 168 | 169 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PatternLockView); 170 | try { 171 | sDotCount = typedArray.getInt(R.styleable.PatternLockView_dotCount, 172 | DEFAULT_PATTERN_DOT_COUNT); 173 | mAspectRatioEnabled = typedArray.getBoolean(R.styleable.PatternLockView_aspectRatioEnabled, 174 | false); 175 | mAspectRatio = typedArray.getInt(R.styleable.PatternLockView_aspectRatio, 176 | ASPECT_RATIO_SQUARE); 177 | mPathWidth = (int) typedArray.getDimension(R.styleable.PatternLockView_pathWidth, 178 | ResourceUtils.getDimensionInPx(getContext(), R.dimen.pattern_lock_path_width)); 179 | mNormalStateColor = typedArray.getColor(R.styleable.PatternLockView_normalStateColor, 180 | ResourceUtils.getColor(getContext(), R.color.white)); 181 | mCorrectStateColor = typedArray.getColor(R.styleable.PatternLockView_correctStateColor, 182 | ResourceUtils.getColor(getContext(), R.color.white)); 183 | mWrongStateColor = typedArray.getColor(R.styleable.PatternLockView_wrongStateColor, 184 | ResourceUtils.getColor(getContext(), R.color.pomegranate)); 185 | mDotNormalSize = (int) typedArray.getDimension(R.styleable.PatternLockView_dotNormalSize, 186 | ResourceUtils.getDimensionInPx(getContext(), R.dimen.pattern_lock_dot_size)); 187 | mDotSelectedSize = (int) typedArray.getDimension(R.styleable 188 | .PatternLockView_dotSelectedSize, 189 | ResourceUtils.getDimensionInPx(getContext(), R.dimen.pattern_lock_dot_selected_size)); 190 | mDotAnimationDuration = typedArray.getInt(R.styleable.PatternLockView_dotAnimationDuration, 191 | DEFAULT_DOT_ANIMATION_DURATION); 192 | mPathEndAnimationDuration = typedArray.getInt(R.styleable.PatternLockView_pathEndAnimationDuration, 193 | DEFAULT_PATH_END_ANIMATION_DURATION); 194 | } finally { 195 | typedArray.recycle(); 196 | } 197 | 198 | // The pattern will always be symmetrical 199 | mPatternSize = sDotCount * sDotCount; 200 | mPattern = new ArrayList<>(mPatternSize); 201 | mPatternDrawLookup = new boolean[sDotCount][sDotCount]; 202 | 203 | mDotStates = new DotState[sDotCount][sDotCount]; 204 | for (int i = 0; i < sDotCount; i++) { 205 | for (int j = 0; j < sDotCount; j++) { 206 | mDotStates[i][j] = new DotState(); 207 | mDotStates[i][j].mSize = mDotNormalSize; 208 | } 209 | } 210 | 211 | mPatternListeners = new ArrayList<>(); 212 | 213 | initView(); 214 | } 215 | 216 | private void initView() { 217 | setClickable(true); 218 | 219 | mPathPaint = new Paint(); 220 | mPathPaint.setAntiAlias(true); 221 | mPathPaint.setDither(true); 222 | mPathPaint.setColor(mNormalStateColor); 223 | mPathPaint.setStyle(Paint.Style.STROKE); 224 | mPathPaint.setStrokeJoin(Paint.Join.ROUND); 225 | mPathPaint.setStrokeCap(Paint.Cap.ROUND); 226 | mPathPaint.setStrokeWidth(mPathWidth); 227 | 228 | mDotPaint = new Paint(); 229 | mDotPaint.setAntiAlias(true); 230 | mDotPaint.setDither(true); 231 | 232 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP 233 | && !isInEditMode()) { 234 | mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator( 235 | getContext(), android.R.interpolator.fast_out_slow_in); 236 | mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( 237 | getContext(), android.R.interpolator.linear_out_slow_in); 238 | } 239 | } 240 | 241 | @Override 242 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 243 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 244 | 245 | if (!mAspectRatioEnabled) { 246 | return; 247 | } 248 | 249 | int oldWidth = resolveMeasured(widthMeasureSpec, getSuggestedMinimumWidth()); 250 | int oldHeight = resolveMeasured(heightMeasureSpec, getSuggestedMinimumHeight()); 251 | 252 | int newWidth; 253 | int newHeight; 254 | switch (mAspectRatio) { 255 | case ASPECT_RATIO_SQUARE: 256 | newWidth = newHeight = Math.min(oldWidth, oldHeight); 257 | break; 258 | case ASPECT_RATIO_WIDTH_BIAS: 259 | newWidth = oldWidth; 260 | newHeight = Math.min(oldWidth, oldHeight); 261 | break; 262 | 263 | case ASPECT_RATIO_HEIGHT_BIAS: 264 | newWidth = Math.min(oldWidth, oldHeight); 265 | newHeight = oldHeight; 266 | break; 267 | 268 | default: 269 | throw new IllegalStateException("Unknown aspect ratio"); 270 | } 271 | setMeasuredDimension(newWidth, newHeight); 272 | } 273 | 274 | @Override 275 | protected void onDraw(Canvas canvas) { 276 | ArrayList pattern = mPattern; 277 | int patternSize = pattern.size(); 278 | boolean[][] drawLookupTable = mPatternDrawLookup; 279 | 280 | if (mPatternViewMode == AUTO_DRAW) { 281 | int oneCycle = (patternSize + 1) * MILLIS_PER_CIRCLE_ANIMATING; 282 | int spotInCycle = (int) (SystemClock.elapsedRealtime() - mAnimatingPeriodStart) 283 | % oneCycle; 284 | int numCircles = spotInCycle / MILLIS_PER_CIRCLE_ANIMATING; 285 | 286 | clearPatternDrawLookup(); 287 | for (int i = 0; i < numCircles; i++) { 288 | Dot dot = pattern.get(i); 289 | drawLookupTable[dot.mRow][dot.mColumn] = true; 290 | } 291 | 292 | boolean needToUpdateInProgressPoint = numCircles > 0 293 | && numCircles < patternSize; 294 | 295 | if (needToUpdateInProgressPoint) { 296 | float percentageOfNextCircle = ((float) (spotInCycle % MILLIS_PER_CIRCLE_ANIMATING)) 297 | / MILLIS_PER_CIRCLE_ANIMATING; 298 | 299 | Dot currentDot = pattern.get(numCircles - 1); 300 | float centerX = getCenterXForColumn(currentDot.mColumn); 301 | float centerY = getCenterYForRow(currentDot.mRow); 302 | 303 | Dot nextDot = pattern.get(numCircles); 304 | float dx = percentageOfNextCircle 305 | * (getCenterXForColumn(nextDot.mColumn) - centerX); 306 | float dy = percentageOfNextCircle 307 | * (getCenterYForRow(nextDot.mRow) - centerY); 308 | mInProgressX = centerX + dx; 309 | mInProgressY = centerY + dy; 310 | } 311 | invalidate(); 312 | } 313 | 314 | Path currentPath = mCurrentPath; 315 | currentPath.rewind(); 316 | 317 | // Draw the dots 318 | for (int i = 0; i < sDotCount; i++) { 319 | float centerY = getCenterYForRow(i); 320 | for (int j = 0; j < sDotCount; j++) { 321 | DotState dotState = mDotStates[i][j]; 322 | float centerX = getCenterXForColumn(j); 323 | float size = dotState.mSize * dotState.mScale; 324 | float translationY = dotState.mTranslateY; 325 | drawCircle(canvas, (int) centerX, (int) centerY + translationY, 326 | size, drawLookupTable[i][j], dotState.mAlpha); 327 | } 328 | } 329 | 330 | // Draw the path of the pattern (unless we are in stealth mode) 331 | boolean drawPath = !mInStealthMode; 332 | if (drawPath) { 333 | mPathPaint.setColor(getCurrentColor(true)); 334 | 335 | boolean anyCircles = false; 336 | float lastX = 0f; 337 | float lastY = 0f; 338 | for (int i = 0; i < patternSize; i++) { 339 | Dot dot = pattern.get(i); 340 | 341 | // Only draw the part of the pattern stored in 342 | // the lookup table (this is only different in case 343 | // of animation) 344 | if (!drawLookupTable[dot.mRow][dot.mColumn]) { 345 | break; 346 | } 347 | anyCircles = true; 348 | 349 | float centerX = getCenterXForColumn(dot.mColumn); 350 | float centerY = getCenterYForRow(dot.mRow); 351 | if (i != 0) { 352 | DotState state = mDotStates[dot.mRow][dot.mColumn]; 353 | currentPath.rewind(); 354 | currentPath.moveTo(lastX, lastY); 355 | if (state.mLineEndX != Float.MIN_VALUE 356 | && state.mLineEndY != Float.MIN_VALUE) { 357 | currentPath.lineTo(state.mLineEndX, state.mLineEndY); 358 | } else { 359 | currentPath.lineTo(centerX, centerY); 360 | } 361 | canvas.drawPath(currentPath, mPathPaint); 362 | } 363 | lastX = centerX; 364 | lastY = centerY; 365 | } 366 | 367 | // Draw last in progress section 368 | if ((mPatternInProgress || mPatternViewMode == AUTO_DRAW) 369 | && anyCircles) { 370 | currentPath.rewind(); 371 | currentPath.moveTo(lastX, lastY); 372 | currentPath.lineTo(mInProgressX, mInProgressY); 373 | 374 | mPathPaint.setAlpha((int) (calculateLastSegmentAlpha( 375 | mInProgressX, mInProgressY, lastX, lastY) * 255f)); 376 | canvas.drawPath(currentPath, mPathPaint); 377 | } 378 | } 379 | } 380 | 381 | @Override 382 | protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { 383 | int adjustedWidth = width - getPaddingLeft() - getPaddingRight(); 384 | mViewWidth = adjustedWidth / (float) sDotCount; 385 | 386 | int adjustedHeight = height - getPaddingTop() - getPaddingBottom(); 387 | mViewHeight = adjustedHeight / (float) sDotCount; 388 | } 389 | 390 | @Override 391 | protected Parcelable onSaveInstanceState() { 392 | Parcelable superState = super.onSaveInstanceState(); 393 | return new SavedState(superState, 394 | PatternLockUtils.patternToString(this, mPattern), 395 | mPatternViewMode, mInputEnabled, mInStealthMode, 396 | mEnableHapticFeedback); 397 | } 398 | 399 | @Override 400 | protected void onRestoreInstanceState(Parcelable state) { 401 | final SavedState savedState = (SavedState) state; 402 | super.onRestoreInstanceState(savedState.getSuperState()); 403 | setPattern(CORRECT, 404 | PatternLockUtils.stringToPattern(this, savedState.getSerializedPattern())); 405 | mPatternViewMode = savedState.getDisplayMode(); 406 | mInputEnabled = savedState.isInputEnabled(); 407 | mInStealthMode = savedState.isInStealthMode(); 408 | mEnableHapticFeedback = savedState.isTactileFeedbackEnabled(); 409 | } 410 | 411 | @Override 412 | public boolean onHoverEvent(MotionEvent event) { 413 | if (((AccessibilityManager) getContext().getSystemService( 414 | Context.ACCESSIBILITY_SERVICE)).isTouchExplorationEnabled()) { 415 | final int action = event.getAction(); 416 | switch (action) { 417 | case MotionEvent.ACTION_HOVER_ENTER: 418 | event.setAction(MotionEvent.ACTION_DOWN); 419 | break; 420 | case MotionEvent.ACTION_HOVER_MOVE: 421 | event.setAction(MotionEvent.ACTION_MOVE); 422 | break; 423 | case MotionEvent.ACTION_HOVER_EXIT: 424 | event.setAction(MotionEvent.ACTION_UP); 425 | break; 426 | } 427 | onTouchEvent(event); 428 | event.setAction(action); 429 | } 430 | return super.onHoverEvent(event); 431 | } 432 | 433 | @Override 434 | public boolean onTouchEvent(MotionEvent event) { 435 | if (!mInputEnabled || !isEnabled()) { 436 | return false; 437 | } 438 | 439 | switch (event.getAction()) { 440 | case MotionEvent.ACTION_DOWN: 441 | handleActionDown(event); 442 | return true; 443 | case MotionEvent.ACTION_UP: 444 | handleActionUp(event); 445 | return true; 446 | case MotionEvent.ACTION_MOVE: 447 | handleActionMove(event); 448 | return true; 449 | case MotionEvent.ACTION_CANCEL: 450 | mPatternInProgress = false; 451 | resetPattern(); 452 | notifyPatternCleared(); 453 | 454 | if (PROFILE_DRAWING) { 455 | if (mDrawingProfilingStarted) { 456 | Debug.stopMethodTracing(); 457 | mDrawingProfilingStarted = false; 458 | } 459 | } 460 | return true; 461 | } 462 | return false; 463 | } 464 | 465 | /** 466 | * Returns the list of dots in the current selected pattern. This list is independent of the 467 | * internal pattern dot list 468 | */ 469 | @SuppressWarnings("unchecked") 470 | public List getPattern() { 471 | return (List) mPattern.clone(); 472 | } 473 | 474 | @PatternViewMode 475 | public int getPatternViewMode() { 476 | return mPatternViewMode; 477 | } 478 | 479 | public boolean isInStealthMode() { 480 | return mInStealthMode; 481 | } 482 | 483 | public boolean isTactileFeedbackEnabled() { 484 | return mEnableHapticFeedback; 485 | } 486 | 487 | public boolean isInputEnabled() { 488 | return mInputEnabled; 489 | } 490 | 491 | public int getDotCount() { 492 | return sDotCount; 493 | } 494 | 495 | public boolean isAspectRatioEnabled() { 496 | return mAspectRatioEnabled; 497 | } 498 | 499 | @AspectRatio 500 | public int getAspectRatio() { 501 | return mAspectRatio; 502 | } 503 | 504 | public int getNormalStateColor() { 505 | return mNormalStateColor; 506 | } 507 | 508 | public int getWrongStateColor() { 509 | return mWrongStateColor; 510 | } 511 | 512 | public int getCorrectStateColor() { 513 | return mCorrectStateColor; 514 | } 515 | 516 | public int getPathWidth() { 517 | return mPathWidth; 518 | } 519 | 520 | public int getDotNormalSize() { 521 | return mDotNormalSize; 522 | } 523 | 524 | public int getDotSelectedSize() { 525 | return mDotSelectedSize; 526 | } 527 | 528 | public int getPatternSize() { 529 | return mPatternSize; 530 | } 531 | 532 | public int getDotAnimationDuration() { 533 | return mDotAnimationDuration; 534 | } 535 | 536 | public int getPathEndAnimationDuration() { 537 | return mPathEndAnimationDuration; 538 | } 539 | 540 | /** 541 | * Set the pattern explicitly rather than waiting for the user to input a 542 | * pattern. You can use this for help or demo purposes 543 | * 544 | * @param patternViewMode The mode in which the pattern should be displayed 545 | * @param pattern The pattern 546 | */ 547 | public void setPattern(@PatternViewMode int patternViewMode, List pattern) { 548 | mPattern.clear(); 549 | mPattern.addAll(pattern); 550 | clearPatternDrawLookup(); 551 | for (Dot dot : pattern) { 552 | mPatternDrawLookup[dot.mRow][dot.mColumn] = true; 553 | } 554 | setViewMode(patternViewMode); 555 | } 556 | 557 | /** 558 | * Set the display mode of the current pattern. This can be useful, for 559 | * instance, after detecting a pattern to tell this view whether change the 560 | * in progress result to correct or wrong. 561 | */ 562 | public void setViewMode(@PatternViewMode int patternViewMode) { 563 | mPatternViewMode = patternViewMode; 564 | if (patternViewMode == AUTO_DRAW) { 565 | if (mPattern.size() == 0) { 566 | throw new IllegalStateException( 567 | "you must have a pattern to " 568 | + "animate if you want to set the display mode to animate"); 569 | } 570 | mAnimatingPeriodStart = SystemClock.elapsedRealtime(); 571 | final Dot first = mPattern.get(0); 572 | mInProgressX = getCenterXForColumn(first.mColumn); 573 | mInProgressY = getCenterYForRow(first.mRow); 574 | clearPatternDrawLookup(); 575 | } 576 | invalidate(); 577 | } 578 | 579 | public void setDotCount(int dotCount) { 580 | sDotCount = dotCount; 581 | mPatternSize = sDotCount * sDotCount; 582 | mPattern = new ArrayList<>(mPatternSize); 583 | mPatternDrawLookup = new boolean[sDotCount][sDotCount]; 584 | 585 | mDotStates = new DotState[sDotCount][sDotCount]; 586 | for (int i = 0; i < sDotCount; i++) { 587 | for (int j = 0; j < sDotCount; j++) { 588 | mDotStates[i][j] = new DotState(); 589 | mDotStates[i][j].mSize = mDotNormalSize; 590 | } 591 | } 592 | 593 | requestLayout(); 594 | invalidate(); 595 | } 596 | 597 | public void setAspectRatioEnabled(boolean aspectRatioEnabled) { 598 | mAspectRatioEnabled = aspectRatioEnabled; 599 | requestLayout(); 600 | } 601 | 602 | public void setAspectRatio(@AspectRatio int aspectRatio) { 603 | mAspectRatio = aspectRatio; 604 | requestLayout(); 605 | } 606 | 607 | public void setNormalStateColor(@ColorInt int normalStateColor) { 608 | mNormalStateColor = normalStateColor; 609 | } 610 | 611 | public void setWrongStateColor(@ColorInt int wrongStateColor) { 612 | mWrongStateColor = wrongStateColor; 613 | } 614 | 615 | public void setCorrectStateColor(@ColorInt int correctStateColor) { 616 | mCorrectStateColor = correctStateColor; 617 | } 618 | 619 | public void setPathWidth(@Dimension int pathWidth) { 620 | mPathWidth = pathWidth; 621 | 622 | initView(); 623 | invalidate(); 624 | } 625 | 626 | public void setDotNormalSize(@Dimension int dotNormalSize) { 627 | mDotNormalSize = dotNormalSize; 628 | 629 | for (int i = 0; i < sDotCount; i++) { 630 | for (int j = 0; j < sDotCount; j++) { 631 | mDotStates[i][j] = new DotState(); 632 | mDotStates[i][j].mSize = mDotNormalSize; 633 | } 634 | } 635 | 636 | invalidate(); 637 | } 638 | 639 | public void setDotSelectedSize(@Dimension int dotSelectedSize) { 640 | mDotSelectedSize = dotSelectedSize; 641 | } 642 | 643 | public void setDotAnimationDuration(int dotAnimationDuration) { 644 | mDotAnimationDuration = dotAnimationDuration; 645 | invalidate(); 646 | } 647 | 648 | public void setPathEndAnimationDuration(int pathEndAnimationDuration) { 649 | mPathEndAnimationDuration = pathEndAnimationDuration; 650 | } 651 | 652 | /** 653 | * Set whether the View is in stealth mode. If {@code true}, there will be 654 | * no visible feedback (path drawing, dot animating, etc) as the user enters the pattern 655 | */ 656 | public void setInStealthMode(boolean inStealthMode) { 657 | mInStealthMode = inStealthMode; 658 | } 659 | 660 | public void setTactileFeedbackEnabled(boolean tactileFeedbackEnabled) { 661 | mEnableHapticFeedback = tactileFeedbackEnabled; 662 | } 663 | 664 | /** 665 | * Enabled/disables any user input of the view. This can be useful to lock the view temporarily 666 | * while showing any message to the user so that the user cannot get the view in 667 | * an unwanted state 668 | */ 669 | public void setInputEnabled(boolean inputEnabled) { 670 | mInputEnabled = inputEnabled; 671 | } 672 | 673 | public void setEnableHapticFeedback(boolean enableHapticFeedback) { 674 | mEnableHapticFeedback = enableHapticFeedback; 675 | } 676 | 677 | public void addPatternLockListener(PatternLockViewListener patternListener) { 678 | mPatternListeners.add(patternListener); 679 | } 680 | 681 | public void removePatternLockListener(PatternLockViewListener patternListener) { 682 | mPatternListeners.remove(patternListener); 683 | } 684 | 685 | public void clearPattern() { 686 | resetPattern(); 687 | } 688 | 689 | private int resolveMeasured(int measureSpec, int desired) { 690 | int result; 691 | int specSize = MeasureSpec.getSize(measureSpec); 692 | switch (MeasureSpec.getMode(measureSpec)) { 693 | case MeasureSpec.UNSPECIFIED: 694 | result = desired; 695 | break; 696 | case MeasureSpec.AT_MOST: 697 | result = Math.max(specSize, desired); 698 | break; 699 | case MeasureSpec.EXACTLY: 700 | default: 701 | result = specSize; 702 | } 703 | return result; 704 | } 705 | 706 | private void notifyPatternProgress() { 707 | sendAccessEvent(R.string.message_pattern_dot_added); 708 | notifyListenersProgress(mPattern); 709 | } 710 | 711 | private void notifyPatternStarted() { 712 | sendAccessEvent(R.string.message_pattern_started); 713 | notifyListenersStarted(); 714 | } 715 | 716 | private void notifyPatternDetected() { 717 | sendAccessEvent(R.string.message_pattern_detected); 718 | notifyListenersComplete(mPattern); 719 | } 720 | 721 | private void notifyPatternCleared() { 722 | sendAccessEvent(R.string.message_pattern_cleared); 723 | notifyListenersCleared(); 724 | } 725 | 726 | private void resetPattern() { 727 | mPattern.clear(); 728 | clearPatternDrawLookup(); 729 | mPatternViewMode = CORRECT; 730 | invalidate(); 731 | } 732 | 733 | private void notifyListenersStarted() { 734 | for (PatternLockViewListener patternListener : mPatternListeners) { 735 | if (patternListener != null) { 736 | patternListener.onStarted(); 737 | } 738 | } 739 | } 740 | 741 | private void notifyListenersProgress(List pattern) { 742 | for (PatternLockViewListener patternListener : mPatternListeners) { 743 | if (patternListener != null) { 744 | patternListener.onProgress(pattern); 745 | } 746 | } 747 | } 748 | 749 | private void notifyListenersComplete(List pattern) { 750 | for (PatternLockViewListener patternListener : mPatternListeners) { 751 | if (patternListener != null) { 752 | patternListener.onComplete(pattern); 753 | } 754 | } 755 | } 756 | 757 | private void notifyListenersCleared() { 758 | for (PatternLockViewListener patternListener : mPatternListeners) { 759 | if (patternListener != null) { 760 | patternListener.onCleared(); 761 | } 762 | } 763 | } 764 | 765 | private void clearPatternDrawLookup() { 766 | for (int i = 0; i < sDotCount; i++) { 767 | for (int j = 0; j < sDotCount; j++) { 768 | mPatternDrawLookup[i][j] = false; 769 | } 770 | } 771 | } 772 | 773 | /** 774 | * Determines whether the point x, y will add a new point to the current 775 | * pattern (in addition to finding the dot, also makes heuristic choices 776 | * such as filling in gaps based on current pattern). 777 | * 778 | * @param x The x coordinate 779 | * @param y The y coordinate 780 | */ 781 | private Dot detectAndAddHit(float x, float y) { 782 | final Dot dot = checkForNewHit(x, y); 783 | if (dot != null) { 784 | // Check for gaps in existing pattern 785 | Dot fillInGapDot = null; 786 | final ArrayList pattern = mPattern; 787 | if (!pattern.isEmpty()) { 788 | Dot lastDot = pattern.get(pattern.size() - 1); 789 | int dRow = dot.mRow - lastDot.mRow; 790 | int dColumn = dot.mColumn - lastDot.mColumn; 791 | 792 | int fillInRow = lastDot.mRow; 793 | int fillInColumn = lastDot.mColumn; 794 | 795 | if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) { 796 | fillInRow = lastDot.mRow + ((dRow > 0) ? 1 : -1); 797 | } 798 | 799 | if (Math.abs(dColumn) == 2 && Math.abs(dRow) != 1) { 800 | fillInColumn = lastDot.mColumn + ((dColumn > 0) ? 1 : -1); 801 | } 802 | 803 | fillInGapDot = Dot.of(fillInRow, fillInColumn); 804 | } 805 | 806 | if (fillInGapDot != null 807 | && !mPatternDrawLookup[fillInGapDot.mRow][fillInGapDot.mColumn]) { 808 | addCellToPattern(fillInGapDot); 809 | } 810 | addCellToPattern(dot); 811 | if (mEnableHapticFeedback) { 812 | performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, 813 | HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING 814 | | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); 815 | } 816 | return dot; 817 | } 818 | return null; 819 | } 820 | 821 | private void addCellToPattern(Dot newDot) { 822 | mPatternDrawLookup[newDot.mRow][newDot.mColumn] = true; 823 | mPattern.add(newDot); 824 | if (!mInStealthMode) { 825 | startDotSelectedAnimation(newDot); 826 | } 827 | notifyPatternProgress(); 828 | } 829 | 830 | private void startDotSelectedAnimation(Dot dot) { 831 | final DotState dotState = mDotStates[dot.mRow][dot.mColumn]; 832 | startSizeAnimation(mDotNormalSize, mDotSelectedSize, mDotAnimationDuration, 833 | mLinearOutSlowInInterpolator, dotState, new Runnable() { 834 | 835 | @Override 836 | public void run() { 837 | startSizeAnimation(mDotSelectedSize, mDotNormalSize, mDotAnimationDuration, 838 | mFastOutSlowInInterpolator, dotState, null); 839 | } 840 | }); 841 | startLineEndAnimation(dotState, mInProgressX, mInProgressY, 842 | getCenterXForColumn(dot.mColumn), getCenterYForRow(dot.mRow)); 843 | } 844 | 845 | private void startLineEndAnimation(final DotState state, 846 | final float startX, final float startY, final float targetX, 847 | final float targetY) { 848 | ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); 849 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 850 | 851 | @Override 852 | public void onAnimationUpdate(ValueAnimator animation) { 853 | float t = (Float) animation.getAnimatedValue(); 854 | state.mLineEndX = (1 - t) * startX + t * targetX; 855 | state.mLineEndY = (1 - t) * startY + t * targetY; 856 | invalidate(); 857 | } 858 | 859 | }); 860 | valueAnimator.addListener(new AnimatorListenerAdapter() { 861 | 862 | @Override 863 | public void onAnimationEnd(Animator animation) { 864 | state.mLineAnimator = null; 865 | } 866 | 867 | }); 868 | valueAnimator.setInterpolator(mFastOutSlowInInterpolator); 869 | valueAnimator.setDuration(mPathEndAnimationDuration); 870 | valueAnimator.start(); 871 | state.mLineAnimator = valueAnimator; 872 | } 873 | 874 | private void startSizeAnimation(float start, float end, long duration, 875 | Interpolator interpolator, final DotState state, 876 | final Runnable endRunnable) { 877 | ValueAnimator valueAnimator = ValueAnimator.ofFloat(start, end); 878 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 879 | 880 | @Override 881 | public void onAnimationUpdate(ValueAnimator animation) { 882 | state.mSize = (Float) animation.getAnimatedValue(); 883 | invalidate(); 884 | } 885 | 886 | }); 887 | if (endRunnable != null) { 888 | valueAnimator.addListener(new AnimatorListenerAdapter() { 889 | 890 | @Override 891 | public void onAnimationEnd(Animator animation) { 892 | if (endRunnable != null) { 893 | endRunnable.run(); 894 | } 895 | } 896 | }); 897 | } 898 | valueAnimator.setInterpolator(interpolator); 899 | valueAnimator.setDuration(duration); 900 | valueAnimator.start(); 901 | } 902 | 903 | /** 904 | * Helper method to map a given x, y to its corresponding cell 905 | * 906 | * @param x The x coordinate 907 | * @param y The y coordinate 908 | * @return 909 | */ 910 | private Dot checkForNewHit(float x, float y) { 911 | final int rowHit = getRowHit(y); 912 | if (rowHit < 0) { 913 | return null; 914 | } 915 | final int columnHit = getColumnHit(x); 916 | if (columnHit < 0) { 917 | return null; 918 | } 919 | 920 | if (mPatternDrawLookup[rowHit][columnHit]) { 921 | return null; 922 | } 923 | return Dot.of(rowHit, columnHit); 924 | } 925 | 926 | /** 927 | * Helper method to find the row that y coordinate falls into 928 | * 929 | * @param y The y coordinate 930 | * @return The mRow that y falls in, or -1 if it falls in no mRow 931 | */ 932 | private int getRowHit(float y) { 933 | final float squareHeight = mViewHeight; 934 | float hitSize = squareHeight * mHitFactor; 935 | 936 | float offset = getPaddingTop() + (squareHeight - hitSize) / 2f; 937 | for (int i = 0; i < sDotCount; i++) { 938 | float hitTop = offset + squareHeight * i; 939 | if (y >= hitTop && y <= hitTop + hitSize) { 940 | return i; 941 | } 942 | } 943 | return -1; 944 | } 945 | 946 | /** 947 | * Helper method to find the column x falls into 948 | * 949 | * @param x The x coordinate 950 | * @return The mColumn that x falls in, or -1 if it falls in no mColumn 951 | */ 952 | private int getColumnHit(float x) { 953 | final float squareWidth = mViewWidth; 954 | float hitSize = squareWidth * mHitFactor; 955 | 956 | float offset = getPaddingLeft() + (squareWidth - hitSize) / 2f; 957 | for (int i = 0; i < sDotCount; i++) { 958 | 959 | final float hitLeft = offset + squareWidth * i; 960 | if (x >= hitLeft && x <= hitLeft + hitSize) { 961 | return i; 962 | } 963 | } 964 | return -1; 965 | } 966 | 967 | private void handleActionMove(MotionEvent event) { 968 | float radius = mPathWidth; 969 | int historySize = event.getHistorySize(); 970 | mTempInvalidateRect.setEmpty(); 971 | boolean invalidateNow = false; 972 | for (int i = 0; i < historySize + 1; i++) { 973 | float x = i < historySize ? event.getHistoricalX(i) : event 974 | .getX(); 975 | float y = i < historySize ? event.getHistoricalY(i) : event 976 | .getY(); 977 | Dot hitDot = detectAndAddHit(x, y); 978 | int patternSize = mPattern.size(); 979 | if (hitDot != null && patternSize == 1) { 980 | mPatternInProgress = true; 981 | notifyPatternStarted(); 982 | } 983 | // Note current x and y for rubber banding of in progress patterns 984 | float dx = Math.abs(x - mInProgressX); 985 | float dy = Math.abs(y - mInProgressY); 986 | if (dx > DEFAULT_DRAG_THRESHOLD || dy > DEFAULT_DRAG_THRESHOLD) { 987 | invalidateNow = true; 988 | } 989 | 990 | if (mPatternInProgress && patternSize > 0) { 991 | final ArrayList pattern = mPattern; 992 | final Dot lastDot = pattern.get(patternSize - 1); 993 | float lastCellCenterX = getCenterXForColumn(lastDot.mColumn); 994 | float lastCellCenterY = getCenterYForRow(lastDot.mRow); 995 | 996 | // Adjust for drawn segment from last cell to (x,y). Radius 997 | // accounts for line width. 998 | float left = Math.min(lastCellCenterX, x) - radius; 999 | float right = Math.max(lastCellCenterX, x) + radius; 1000 | float top = Math.min(lastCellCenterY, y) - radius; 1001 | float bottom = Math.max(lastCellCenterY, y) + radius; 1002 | 1003 | // Invalidate between the pattern's new cell and the pattern's 1004 | // previous cell 1005 | if (hitDot != null) { 1006 | float width = mViewWidth * 0.5f; 1007 | float height = mViewHeight * 0.5f; 1008 | float hitCellCenterX = getCenterXForColumn(hitDot.mColumn); 1009 | float hitCellCenterY = getCenterYForRow(hitDot.mRow); 1010 | 1011 | left = Math.min(hitCellCenterX - width, left); 1012 | right = Math.max(hitCellCenterX + width, right); 1013 | top = Math.min(hitCellCenterY - height, top); 1014 | bottom = Math.max(hitCellCenterY + height, bottom); 1015 | } 1016 | 1017 | // Invalidate between the pattern's last cell and the previous 1018 | // location 1019 | mTempInvalidateRect.union(Math.round(left), Math.round(top), 1020 | Math.round(right), Math.round(bottom)); 1021 | } 1022 | } 1023 | mInProgressX = event.getX(); 1024 | mInProgressY = event.getY(); 1025 | 1026 | // To save updates, we only invalidate if the user moved beyond a 1027 | // certain amount. 1028 | if (invalidateNow) { 1029 | mInvalidate.union(mTempInvalidateRect); 1030 | invalidate(mInvalidate); 1031 | mInvalidate.set(mTempInvalidateRect); 1032 | } 1033 | } 1034 | 1035 | private void sendAccessEvent(int resId) { 1036 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { 1037 | setContentDescription(getContext().getString(resId)); 1038 | sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 1039 | setContentDescription(null); 1040 | } else { 1041 | announceForAccessibility(getContext().getString(resId)); 1042 | } 1043 | } 1044 | 1045 | private void handleActionUp(MotionEvent event) { 1046 | // Report pattern detected 1047 | if (!mPattern.isEmpty()) { 1048 | mPatternInProgress = false; 1049 | cancelLineAnimations(); 1050 | notifyPatternDetected(); 1051 | invalidate(); 1052 | } 1053 | if (PROFILE_DRAWING) { 1054 | if (mDrawingProfilingStarted) { 1055 | Debug.stopMethodTracing(); 1056 | mDrawingProfilingStarted = false; 1057 | } 1058 | } 1059 | } 1060 | 1061 | private void cancelLineAnimations() { 1062 | for (int i = 0; i < sDotCount; i++) { 1063 | for (int j = 0; j < sDotCount; j++) { 1064 | DotState state = mDotStates[i][j]; 1065 | if (state.mLineAnimator != null) { 1066 | state.mLineAnimator.cancel(); 1067 | state.mLineEndX = Float.MIN_VALUE; 1068 | state.mLineEndY = Float.MIN_VALUE; 1069 | } 1070 | } 1071 | } 1072 | } 1073 | 1074 | private void handleActionDown(MotionEvent event) { 1075 | resetPattern(); 1076 | float x = event.getX(); 1077 | float y = event.getY(); 1078 | Dot hitDot = detectAndAddHit(x, y); 1079 | if (hitDot != null) { 1080 | mPatternInProgress = true; 1081 | mPatternViewMode = CORRECT; 1082 | notifyPatternStarted(); 1083 | } else { 1084 | mPatternInProgress = false; 1085 | notifyPatternCleared(); 1086 | } 1087 | if (hitDot != null) { 1088 | float startX = getCenterXForColumn(hitDot.mColumn); 1089 | float startY = getCenterYForRow(hitDot.mRow); 1090 | 1091 | float widthOffset = mViewWidth / 2f; 1092 | float heightOffset = mViewHeight / 2f; 1093 | 1094 | invalidate((int) (startX - widthOffset), 1095 | (int) (startY - heightOffset), 1096 | (int) (startX + widthOffset), (int) (startY + heightOffset)); 1097 | } 1098 | mInProgressX = x; 1099 | mInProgressY = y; 1100 | if (PROFILE_DRAWING) { 1101 | if (!mDrawingProfilingStarted) { 1102 | Debug.startMethodTracing("PatternLockDrawing"); 1103 | mDrawingProfilingStarted = true; 1104 | } 1105 | } 1106 | } 1107 | 1108 | private float getCenterXForColumn(int column) { 1109 | return getPaddingLeft() + column * mViewWidth + mViewWidth / 2f; 1110 | } 1111 | 1112 | private float getCenterYForRow(int row) { 1113 | return getPaddingTop() + row * mViewHeight + mViewHeight / 2f; 1114 | } 1115 | 1116 | private float calculateLastSegmentAlpha(float x, float y, float lastX, 1117 | float lastY) { 1118 | float diffX = x - lastX; 1119 | float diffY = y - lastY; 1120 | float dist = (float) Math.sqrt(diffX * diffX + diffY * diffY); 1121 | float fraction = dist / mViewWidth; 1122 | return Math.min(1f, Math.max(0f, (fraction - 0.3f) * 4f)); 1123 | } 1124 | 1125 | private int getCurrentColor(boolean partOfPattern) { 1126 | if (!partOfPattern || mInStealthMode || mPatternInProgress) { 1127 | return mNormalStateColor; 1128 | } else if (mPatternViewMode == WRONG) { 1129 | return mWrongStateColor; 1130 | } else if (mPatternViewMode == CORRECT 1131 | || mPatternViewMode == AUTO_DRAW) { 1132 | return mCorrectStateColor; 1133 | } else { 1134 | throw new IllegalStateException("Unknown view mode " + mPatternViewMode); 1135 | } 1136 | } 1137 | 1138 | private void drawCircle(Canvas canvas, float centerX, float centerY, 1139 | float size, boolean partOfPattern, float alpha) { 1140 | mDotPaint.setColor(getCurrentColor(partOfPattern)); 1141 | mDotPaint.setAlpha((int) (alpha * 255)); 1142 | canvas.drawCircle(centerX, centerY, size / 2, mDotPaint); 1143 | } 1144 | 1145 | /** 1146 | * Represents a cell in the matrix of the pattern view 1147 | */ 1148 | public static class Dot implements Parcelable { 1149 | 1150 | private int mRow; 1151 | private int mColumn; 1152 | private static Dot[][] sDots; 1153 | 1154 | static { 1155 | sDots = new Dot[sDotCount][sDotCount]; 1156 | 1157 | // Initializing the dots 1158 | for (int i = 0; i < sDotCount; i++) { 1159 | for (int j = 0; j < sDotCount; j++) { 1160 | sDots[i][j] = new Dot(i, j); 1161 | } 1162 | } 1163 | } 1164 | 1165 | private Dot(int row, int column) { 1166 | checkRange(row, column); 1167 | this.mRow = row; 1168 | this.mColumn = column; 1169 | } 1170 | 1171 | /** 1172 | * Gets the identifier of the dot. It is counted from left to right, top to bottom of the 1173 | * matrix, starting by zero 1174 | */ 1175 | public int getId() { 1176 | return mRow * sDotCount + mColumn; 1177 | } 1178 | 1179 | public int getRow() { 1180 | return mRow; 1181 | } 1182 | 1183 | public int getColumn() { 1184 | return mColumn; 1185 | } 1186 | 1187 | /** 1188 | * @param row The mRow of the cell. 1189 | * @param column The mColumn of the cell. 1190 | */ 1191 | public static synchronized Dot of(int row, int column) { 1192 | checkRange(row, column); 1193 | return sDots[row][column]; 1194 | } 1195 | 1196 | /** 1197 | * Gets a cell from its identifier 1198 | */ 1199 | public static synchronized Dot of(int id) { 1200 | return of(id / sDotCount, id % sDotCount); 1201 | } 1202 | 1203 | private static void checkRange(int row, int column) { 1204 | if (row < 0 || row > sDotCount - 1) { 1205 | throw new IllegalArgumentException("mRow must be in range 0-" 1206 | + (sDotCount - 1)); 1207 | } 1208 | if (column < 0 || column > sDotCount - 1) { 1209 | throw new IllegalArgumentException("mColumn must be in range 0-" 1210 | + (sDotCount - 1)); 1211 | } 1212 | } 1213 | 1214 | @Override 1215 | public String toString() { 1216 | return "(Row = " + mRow + ", Col = " + mColumn + ")"; 1217 | } 1218 | 1219 | @Override 1220 | public boolean equals(Object object) { 1221 | if (object instanceof Dot) 1222 | return mColumn == ((Dot) object).mColumn 1223 | && mRow == ((Dot) object).mRow; 1224 | return super.equals(object); 1225 | } 1226 | 1227 | @Override 1228 | public int hashCode() { 1229 | int result = mRow; 1230 | result = 31 * result + mColumn; 1231 | return result; 1232 | } 1233 | 1234 | @Override 1235 | public int describeContents() { 1236 | return 0; 1237 | } 1238 | 1239 | @Override 1240 | public void writeToParcel(Parcel dest, int flags) { 1241 | dest.writeInt(mColumn); 1242 | dest.writeInt(mRow); 1243 | } 1244 | 1245 | public static final Creator CREATOR = new Creator() { 1246 | 1247 | public Dot createFromParcel(Parcel in) { 1248 | return new Dot(in); 1249 | } 1250 | 1251 | public Dot[] newArray(int size) { 1252 | return new Dot[size]; 1253 | } 1254 | }; 1255 | 1256 | private Dot(Parcel in) { 1257 | mColumn = in.readInt(); 1258 | mRow = in.readInt(); 1259 | } 1260 | } 1261 | 1262 | /** 1263 | * The parcelable for saving and restoring a lock pattern view 1264 | */ 1265 | private static class SavedState extends BaseSavedState { 1266 | 1267 | private final String mSerializedPattern; 1268 | private final int mDisplayMode; 1269 | private final boolean mInputEnabled; 1270 | private final boolean mInStealthMode; 1271 | private final boolean mTactileFeedbackEnabled; 1272 | 1273 | /** 1274 | * Constructor called from {@link PatternLockView#onSaveInstanceState()} 1275 | */ 1276 | private SavedState(Parcelable superState, String serializedPattern, 1277 | int displayMode, boolean inputEnabled, boolean inStealthMode, 1278 | boolean tactileFeedbackEnabled) { 1279 | super(superState); 1280 | 1281 | mSerializedPattern = serializedPattern; 1282 | mDisplayMode = displayMode; 1283 | mInputEnabled = inputEnabled; 1284 | mInStealthMode = inStealthMode; 1285 | mTactileFeedbackEnabled = tactileFeedbackEnabled; 1286 | } 1287 | 1288 | /** 1289 | * Constructor called from {@link #CREATOR} 1290 | */ 1291 | private SavedState(Parcel in) { 1292 | super(in); 1293 | 1294 | mSerializedPattern = in.readString(); 1295 | mDisplayMode = in.readInt(); 1296 | mInputEnabled = (Boolean) in.readValue(null); 1297 | mInStealthMode = (Boolean) in.readValue(null); 1298 | mTactileFeedbackEnabled = (Boolean) in.readValue(null); 1299 | } 1300 | 1301 | public String getSerializedPattern() { 1302 | return mSerializedPattern; 1303 | } 1304 | 1305 | public int getDisplayMode() { 1306 | return mDisplayMode; 1307 | } 1308 | 1309 | public boolean isInputEnabled() { 1310 | return mInputEnabled; 1311 | } 1312 | 1313 | public boolean isInStealthMode() { 1314 | return mInStealthMode; 1315 | } 1316 | 1317 | public boolean isTactileFeedbackEnabled() { 1318 | return mTactileFeedbackEnabled; 1319 | } 1320 | 1321 | @Override 1322 | public void writeToParcel(Parcel dest, int flags) { 1323 | super.writeToParcel(dest, flags); 1324 | dest.writeString(mSerializedPattern); 1325 | dest.writeInt(mDisplayMode); 1326 | dest.writeValue(mInputEnabled); 1327 | dest.writeValue(mInStealthMode); 1328 | dest.writeValue(mTactileFeedbackEnabled); 1329 | } 1330 | 1331 | @SuppressWarnings("unused") 1332 | public static final Creator CREATOR = new Creator() { 1333 | 1334 | public SavedState createFromParcel(Parcel in) { 1335 | return new SavedState(in); 1336 | } 1337 | 1338 | public SavedState[] newArray(int size) { 1339 | return new SavedState[size]; 1340 | } 1341 | }; 1342 | } 1343 | 1344 | public static class DotState { 1345 | float mScale = 1.0f; 1346 | float mTranslateY = 0.0f; 1347 | float mAlpha = 1.0f; 1348 | float mSize; 1349 | float mLineEndX = Float.MIN_VALUE; 1350 | float mLineEndY = Float.MIN_VALUE; 1351 | ValueAnimator mLineAnimator; 1352 | } 1353 | } 1354 | -------------------------------------------------------------------------------- /patternlockview/src/main/java/com/andrognito/patternlockview/listener/PatternLockViewListener.java: -------------------------------------------------------------------------------- 1 | package com.andrognito.patternlockview.listener; 2 | 3 | /** 4 | * Created by aritraroy on 19/03/17. 5 | */ 6 | 7 | import com.andrognito.patternlockview.PatternLockView; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * The callback interface for detecting patterns entered by the user 13 | */ 14 | public interface PatternLockViewListener { 15 | 16 | /** 17 | * Fired when the pattern drawing has just started 18 | */ 19 | void onStarted(); 20 | 21 | /** 22 | * Fired when the pattern is still being drawn and progressed to 23 | * one more {@link com.andrognito.patternlockview.PatternLockView.Dot} 24 | */ 25 | void onProgress(List progressPattern); 26 | 27 | /** 28 | * Fired when the user has completed drawing the pattern and has moved their finger away 29 | * from the view 30 | */ 31 | void onComplete(List pattern); 32 | 33 | /** 34 | * Fired when the patten has been cleared from the view 35 | */ 36 | void onCleared(); 37 | } -------------------------------------------------------------------------------- /patternlockview/src/main/java/com/andrognito/patternlockview/utils/PatternLockUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.andrognito.patternlockview.utils; 18 | 19 | import com.andrognito.patternlockview.PatternLockView; 20 | 21 | import java.io.UnsupportedEncodingException; 22 | import java.math.BigInteger; 23 | import java.security.MessageDigest; 24 | import java.security.NoSuchAlgorithmException; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.Locale; 28 | 29 | public class PatternLockUtils { 30 | 31 | private static final String UTF8 = "UTF-8"; 32 | private static final String SHA1 = "SHA-1"; 33 | private static final String MD5 = "MD5"; 34 | 35 | private PatternLockUtils() { 36 | throw new AssertionError("You can not instantiate this class. Use its static utility " + 37 | "methods instead"); 38 | } 39 | 40 | /** 41 | * Serializes a given pattern to its equivalent string representation. You can store this string 42 | * in any persistence storage or send it to the server for verification 43 | * 44 | * @param pattern The actual pattern 45 | * @return The pattern in its string form 46 | */ 47 | public static String patternToString(PatternLockView patternLockView, 48 | List pattern) { 49 | if (pattern == null) { 50 | return ""; 51 | } 52 | int patternSize = pattern.size(); 53 | StringBuilder stringBuilder = new StringBuilder(); 54 | 55 | for (int i = 0; i < patternSize; i++) { 56 | PatternLockView.Dot dot = pattern.get(i); 57 | stringBuilder.append((dot.getRow() * patternLockView.getDotCount() + dot.getColumn())); 58 | } 59 | return stringBuilder.toString(); 60 | } 61 | 62 | /** 63 | * De-serializes a given string to its equivalent pattern representation 64 | * 65 | * @param string The pattern serialized with {@link #patternToString} 66 | * @return The actual pattern 67 | */ 68 | public static List stringToPattern(PatternLockView patternLockView, 69 | String string) { 70 | List result = new ArrayList<>(); 71 | 72 | for (int i = 0; i < string.length(); i++) { 73 | int number = Character.getNumericValue(string.charAt(i)); 74 | result.add(PatternLockView.Dot.of(number / patternLockView.getDotCount(), 75 | number % patternLockView.getDotCount())); 76 | } 77 | return result; 78 | } 79 | 80 | /** 81 | * Serializes a given pattern to its equivalent SHA-1 representation. You can store this string 82 | * in any persistence storage or send it to the server for verification 83 | * 84 | * @param pattern The actual pattern 85 | * @return The SHA-1 string of the pattern 86 | */ 87 | public static String patternToSha1(PatternLockView patternLockView, 88 | List pattern) { 89 | try { 90 | MessageDigest messageDigest = MessageDigest.getInstance(SHA1); 91 | messageDigest.update(patternToString(patternLockView, pattern).getBytes(UTF8)); 92 | 93 | byte[] digest = messageDigest.digest(); 94 | BigInteger bigInteger = new BigInteger(1, digest); 95 | return String.format((Locale) null, 96 | "%0" + (digest.length * 2) + "x", bigInteger).toLowerCase(); 97 | } catch (NoSuchAlgorithmException e) { 98 | return null; 99 | } catch (UnsupportedEncodingException e) { 100 | return null; 101 | } 102 | } 103 | 104 | /** 105 | * Serializes a given pattern to its equivalent MD5 representation. You can store this string 106 | * in any persistence storage or send it to the server for verification 107 | * 108 | * @param pattern The actual pattern 109 | * @return The MD5 string of the pattern 110 | */ 111 | public static String patternToMD5(PatternLockView patternLockView, 112 | List pattern) { 113 | try { 114 | MessageDigest messageDigest = MessageDigest.getInstance(MD5); 115 | messageDigest.update(patternToString(patternLockView, pattern).getBytes(UTF8)); 116 | 117 | byte[] digest = messageDigest.digest(); 118 | BigInteger bigInteger = new BigInteger(1, digest); 119 | return String.format((Locale) null, 120 | "%0" + (digest.length * 2) + "x", bigInteger).toLowerCase(); 121 | } catch (NoSuchAlgorithmException e) { 122 | return null; 123 | } catch (UnsupportedEncodingException e) { 124 | return null; 125 | } 126 | } 127 | 128 | /** 129 | * Generates a random "CAPTCHA" pattern. The generated pattern is easy for the user to re-draw. 130 | *

131 | * NOTE: This method is not optimized and not benchmarked yet for large mSize 132 | * of the pattern's matrix. Currently it works fine with a matrix of {@code 3x3} cells. 133 | * Be careful when the mSize increases.

134 | */ 135 | public static ArrayList generateRandomPattern(PatternLockView patternLockView, 136 | int size) 137 | throws IndexOutOfBoundsException { 138 | if (patternLockView == null) { 139 | throw new IllegalArgumentException("PatternLockView can not be null."); 140 | } 141 | 142 | if (size <= 0 || size > patternLockView.getDotCount()) { 143 | throw new IndexOutOfBoundsException("Size must be in range [1, " + 144 | patternLockView.getDotCount() + "]"); 145 | } 146 | 147 | List usedIds = new ArrayList<>(); 148 | int lastId = RandomUtils.randInt(patternLockView.getDotCount()); 149 | usedIds.add(lastId); 150 | 151 | while (usedIds.size() < size) { 152 | // We start from an empty matrix, so there's always a break point to 153 | // exit this loop 154 | final int lastRow = lastId / patternLockView.getDotCount(); 155 | final int lastCol = lastId % patternLockView.getDotCount(); 156 | 157 | // This is the max available rows/ columns that we can reach from 158 | // the cell of `lastId` to the border of the matrix. 159 | final int maxDistance = Math.max( 160 | Math.max(lastRow, patternLockView.getDotCount() - lastRow), 161 | Math.max(lastCol, patternLockView.getDotCount() - lastCol)); 162 | 163 | lastId = -1; 164 | 165 | // Starting from `distance` = 1, find the closest-available 166 | // neighbour value of the cell [lastRow, lastCol]. 167 | for (int distance = 1; distance <= maxDistance; distance++) { 168 | 169 | // Now we have a square surrounding the current cell. We call it 170 | // ABCD, in which A is top-left, and C is bottom-right. 171 | final int rowA = lastRow - distance; 172 | final int colA = lastCol - distance; 173 | final int rowC = lastRow + distance; 174 | final int colC = lastCol + distance; 175 | 176 | int[] randomValues; 177 | 178 | // Process randomly AB, BC, CD, and DA. Break the loop as soon 179 | // as we find one value. 180 | final int[] lines = RandomUtils.randIntArray(4); 181 | for (int line : lines) { 182 | switch (line) { 183 | case 0: { 184 | if (rowA >= 0) { 185 | randomValues = RandomUtils.randIntArray(Math.max(0, colA), 186 | Math.min(patternLockView.getDotCount(), 187 | colC + 1)); 188 | for (int c : randomValues) { 189 | lastId = rowA * patternLockView.getDotCount() 190 | + c; 191 | if (usedIds.contains(lastId)) 192 | lastId = -1; 193 | else 194 | break; 195 | } 196 | } 197 | 198 | break; 199 | } 200 | 201 | case 1: { 202 | if (colC < patternLockView.getDotCount()) { 203 | randomValues = RandomUtils.randIntArray(Math.max(0, rowA + 1), 204 | Math.min(patternLockView.getDotCount(), 205 | rowC + 1)); 206 | for (int r : randomValues) { 207 | lastId = r * patternLockView.getDotCount() 208 | + colC; 209 | if (usedIds.contains(lastId)) 210 | lastId = -1; 211 | else 212 | break; 213 | } 214 | } 215 | 216 | break; 217 | } 218 | 219 | case 2: { 220 | if (rowC < patternLockView.getDotCount()) { 221 | randomValues = RandomUtils.randIntArray(Math.max(0, colA), 222 | Math.min(patternLockView.getDotCount(), 223 | colC)); 224 | for (int c : randomValues) { 225 | lastId = rowC * patternLockView.getDotCount() 226 | + c; 227 | if (usedIds.contains(lastId)) 228 | lastId = -1; 229 | else 230 | break; 231 | } 232 | } 233 | 234 | break; 235 | } 236 | 237 | case 3: { 238 | if (colA >= 0) { 239 | randomValues = RandomUtils.randIntArray(Math.max(0, rowA + 1), 240 | Math.min(patternLockView.getDotCount(), 241 | rowC)); 242 | for (int r : randomValues) { 243 | lastId = r * patternLockView.getDotCount() 244 | + colA; 245 | if (usedIds.contains(lastId)) 246 | lastId = -1; 247 | else 248 | break; 249 | } 250 | } 251 | 252 | break; 253 | } 254 | } 255 | 256 | if (lastId >= 0) break; 257 | } 258 | 259 | if (lastId >= 0) break; 260 | } 261 | 262 | usedIds.add(lastId); 263 | } 264 | 265 | ArrayList result = new ArrayList<>(); 266 | for (int id : usedIds) { 267 | result.add(PatternLockView.Dot.of(id)); 268 | } 269 | 270 | return result; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /patternlockview/src/main/java/com/andrognito/patternlockview/utils/RandomUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Hai Bison 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.andrognito.patternlockview.utils; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.Random; 22 | 23 | /** 24 | * Random utilities. 25 | */ 26 | public class RandomUtils { 27 | 28 | private static final Random RANDOM = new Random(); 29 | 30 | private RandomUtils() { 31 | throw new AssertionError("You can not instantiate this class. Use its static utility " + 32 | "methods instead"); 33 | } 34 | 35 | /** 36 | * Generates a random integer 37 | */ 38 | public static int randInt() { 39 | return RANDOM.nextInt((int) (System.nanoTime() % Integer.MAX_VALUE)); 40 | } 41 | 42 | /** 43 | * Generates a random integer within {@code [0, max)}. 44 | * 45 | * @param max The maximum bound 46 | * @return A random integer 47 | */ 48 | public static int randInt(int max) { 49 | return max > 0 ? randInt() % max : 0; 50 | } 51 | 52 | /** 53 | * Generates a random integer array which has length of {@code end - start}, 54 | * and is filled by all values from {@code start} to {@code end - 1} in randomized orders. 55 | * 56 | * @param start The starting value 57 | * @param end The ending value 58 | * @return The random integer array. If {@code end <= start}, an empty array is returned 59 | */ 60 | public static int[] randIntArray(int start, int end) { 61 | if (end <= start) { 62 | return new int[0]; 63 | } 64 | 65 | final List values = new ArrayList<>(); 66 | for (int i = start; i < end; i++) { 67 | values.add(i); 68 | } 69 | 70 | final int[] result = new int[values.size()]; 71 | for (int i = 0; i < result.length; i++) { 72 | int k = randInt(values.size()); 73 | result[i] = values.get(k); 74 | values.remove(k); 75 | } 76 | 77 | return result; 78 | } 79 | 80 | /** 81 | * Generates a random integer array which has length of {@code end}, 82 | * and is filled by all values from {@code 0} to {@code end - 1} in randomized orders. 83 | * 84 | * @param end The ending value 85 | * @return The random integer array. If {@code end <= start}, an empty array is returned 86 | */ 87 | public static int[] randIntArray(int end) { 88 | return randIntArray(0, end); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /patternlockview/src/main/java/com/andrognito/patternlockview/utils/ResourceUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Hai Bison 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.andrognito.patternlockview.utils; 18 | 19 | import android.content.Context; 20 | import android.support.annotation.ColorRes; 21 | import android.support.annotation.DimenRes; 22 | import android.support.annotation.NonNull; 23 | import android.support.annotation.StringRes; 24 | import android.support.v4.content.ContextCompat; 25 | 26 | public class ResourceUtils { 27 | 28 | private ResourceUtils() { 29 | throw new AssertionError("You can not instantiate this class. Use its static utility " + 30 | "methods instead"); 31 | } 32 | 33 | /** 34 | * Get color from a resource id 35 | * 36 | * @param context The context 37 | * @param colorRes The resource identifier of the color 38 | * @return The resolved color value 39 | */ 40 | public static int getColor(@NonNull Context context, @ColorRes int colorRes) { 41 | return ContextCompat.getColor(context, colorRes); 42 | } 43 | 44 | /** 45 | * Get string from a resource id 46 | * 47 | * @param context The context 48 | * @param stringRes The resource identifier of the string 49 | * @return The string value 50 | */ 51 | public static String getString(@NonNull Context context, @StringRes int stringRes) { 52 | return context.getString(stringRes); 53 | } 54 | 55 | /** 56 | * Get dimension in pixels from its resource id 57 | * 58 | * @param context The context 59 | * @param dimenRes The resource identifier of the dimension 60 | * @return The dimension in pixels 61 | */ 62 | public static float getDimensionInPx(@NonNull Context context, @DimenRes int dimenRes) { 63 | return context.getResources().getDimension(dimenRes); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /patternlockview/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /patternlockview/src/main/res/values/color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFF 4 | #f4511e 5 | -------------------------------------------------------------------------------- /patternlockview/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 3dp 4 | 10dp 5 | 24dp 6 | -------------------------------------------------------------------------------- /patternlockview/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PatternLockView 3 | Dot added to pattern 4 | Pattern drawing started 5 | Pattern drawing completed 6 | Pattern cleared 7 | 8 | -------------------------------------------------------------------------------- /screenshots/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/screenshots/logo.png -------------------------------------------------------------------------------- /screenshots/pattern-lock-view-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/screenshots/pattern-lock-view-banner.png -------------------------------------------------------------------------------- /screenshots/pattern_lock_view_2_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/screenshots/pattern_lock_view_2_small.gif -------------------------------------------------------------------------------- /screenshots/pattern_lock_view_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aritraroy/PatternLockView/a90b0d4bf0f286bc23e0356a260b6929adebe7de/screenshots/pattern_lock_view_small.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':patternlockview', ':patternlockview-reactive' 2 | --------------------------------------------------------------------------------