├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── lollipin │ │ └── orangegangsters │ │ └── github │ │ └── com │ │ └── lollipin │ │ └── functional │ │ ├── AbstractTest.java │ │ └── PinLockTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── omadahealth │ │ └── lollipin │ │ ├── CustomApplication.java │ │ ├── CustomPinActivity.java │ │ ├── LockedCompatActivity.java │ │ ├── MainActivity.java │ │ └── NotLockedActivity.java │ └── res │ ├── drawable-hdpi │ ├── ic_menu_white_36dp.png │ └── icon.png │ ├── drawable-mdpi │ ├── ic_menu_white_36dp.png │ └── icon.png │ ├── drawable-xhdpi │ ├── ic_menu_white_36dp.png │ ├── icon.png │ └── security_lock.png │ ├── drawable-xxhdpi │ ├── ic_menu_white_36dp.png │ └── icon.png │ ├── drawable-xxxhdpi │ └── ic_menu_white_36dp.png │ ├── layout │ ├── activity_compat_locked.xml │ ├── activity_main.xml │ └── activity_not_locked.xml │ ├── menu │ └── menu_main.xml │ ├── raw │ ├── github_gif.gif │ └── github_gif2.gif │ ├── values-v21 │ └── styles.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── omadahealth │ │ └── lollipin │ │ └── lib │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── omadahealth │ │ └── lollipin │ │ └── lib │ │ ├── PinActivity.java │ │ ├── PinCompatActivity.java │ │ ├── PinFragmentActivity.java │ │ ├── encryption │ │ └── Encryptor.java │ │ ├── enums │ │ ├── Algorithm.java │ │ └── KeyboardButtonEnum.java │ │ ├── interfaces │ │ ├── KeyboardButtonClickedListener.java │ │ └── LifeCycleInterface.java │ │ ├── managers │ │ ├── AppLock.java │ │ ├── AppLockActivity.java │ │ ├── AppLockImpl.java │ │ ├── FingerprintUiHelper.java │ │ └── LockManager.java │ │ └── views │ │ ├── KeyboardButtonView.java │ │ ├── KeyboardView.java │ │ ├── PinCodeRoundView.java │ │ ├── PinCodeView.java │ │ └── SquareImageView.java │ └── res │ ├── anim │ ├── cycle5.xml │ ├── nothing.xml │ ├── shake.xml │ └── slide_down.xml │ ├── drawable-hdpi │ ├── ic_backspace_grey600_24dp.png │ ├── ic_fp_40px.png │ └── tile.9.png │ ├── drawable-mdpi │ ├── ic_backspace_grey600_24dp.png │ └── ic_fp_40px.png │ ├── drawable-xhdpi │ ├── ic_backspace_grey600_24dp.png │ └── ic_fp_40px.png │ ├── drawable-xxhdpi │ ├── ic_backspace_grey600_24dp.png │ └── ic_fp_40px.png │ ├── drawable-xxxhdpi │ ├── ic_backspace_grey600_24dp.png │ └── ic_fp_40px.png │ ├── drawable │ ├── ic_fingerprint_error.xml │ ├── ic_fingerprint_success.xml │ ├── pin_code_round_empty.xml │ └── pin_code_round_full.xml │ ├── layout │ ├── activity_pin_code.xml │ ├── view_keyboard.xml │ ├── view_keyboard_button.xml │ ├── view_round.xml │ └── view_round_pin_code.xml │ ├── values-id │ └── strings.xml │ ├── values-ko │ └── strings.xml │ ├── values-pt │ └── strings.xml │ ├── values-ru │ └── strings.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── integers.xml │ └── strings.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | build/ 18 | .gradle/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | .idea 23 | build/ 24 | 25 | # Local configuration file (sdk path, etc) 26 | local.properties 27 | keystore.properties 28 | 29 | # Proguard folder generated by Eclipse 30 | proguard/ 31 | 32 | # Log Files 33 | *.log 34 | 35 | # Idea project files 36 | *.iml 37 | .idea 38 | *.iws 39 | 40 | # Eclipse project files 41 | default.properties 42 | .metadata 43 | .settings 44 | .project 45 | .classpath 46 | 47 | # Junk 48 | .DS_Store 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 OrangeGangsters 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LolliPin [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-LolliPin-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/1425) 2 | ================ 3 | 4 | A Lollipop material design styled android pincode library (API 14+) 5 | 6 | 7 | To include in your project, add this to your build.gradle file: 8 | 9 | ``` 10 | //Lollipin 11 | compile ('com.github.orangegangsters:lollipin:2.0.0@aar') { 12 | transitive = true 13 | } 14 | ``` 15 | Starting from version 2.1.0 we will have a different package name: 16 | 17 | ``` 18 | //Lollipin 19 | compile ('com.github.omadahealth:lollipin:2.1.0@aar') { 20 | transitive = true 21 | } 22 | ``` 23 | 24 | ![Demo](app/src/main/res/raw/github_gif.gif) ![Demo](app/src/main/res/raw/github_gif2.gif) 25 | 26 | ======== 27 | ### By 28 | Developers: 29 | [Olivier Goutay](https://github.com/olivierg13) and [Stoyan Dimitrov](https://github.com/StoyanD) and [Dae Park](https://github.com/daespark) 30 | 31 | Contributors: 32 | [Art Beatte IV](https://github.com/abeatte), [Alex Morgan](https://github.com/axemorgan) 33 | 34 | Designers: 35 | [Yassine Bentaieb](http://yassinebentaieb.com/) 36 | 37 | ======== 38 | ### Security 39 | 40 | ##### Password protection 41 | The password itself is not saved, only its hash using the SHA-1 algorithm. 42 | This hash is then saved on the SharedPreferences, allowing to verify that the user entered the right PinCode, 43 | without giving the possibility to retrieve it. 44 | 45 | ##### Introducing Fingerprint 46 | Once the user has enabled the password, he can also use his fingerprint scanner (using Google Api, not Samsung) 47 | to unlock his device. 48 | 49 | ======== 50 | ### Usage 51 | 52 | If you want an example on how to use it, you can find an example app in this repo. 53 | 54 | ======== 55 | #### Preparing dependencies 56 | 57 | We are using a custom version of RippleView that contains a RippleAnimationListener. 58 | In order to be able to fetch this dependency, you need to add these lines into your main build.gradle file: 59 | ``` 60 | allprojects { 61 | repositories { 62 | maven{ 63 | url "https://github.com/omadahealth/omada-nexus/raw/master/release" 64 | } 65 | jcenter() 66 | 67 | } 68 | } 69 | ``` 70 | 71 | ======== 72 | #### Overriding the AppLockActivity 73 | 74 | In order to use the "Forgot" system, we let you extend the AppLockActivity class to provide your own way of handling the user behaviour in this case (logout, delete datas etc...) 75 | 76 | ``` 77 | public class CustomPinActivity extends AppLockActivity { 78 | @Override 79 | public void showForgotDialog() { 80 | //Launch your popup or anything you want here 81 | } 82 | } 83 | ``` 84 | 85 | ======== 86 | #### Init 87 | 88 | Advised to be done by extending the Application, but can be done elsewhere. The method below provides a way to enable or disable the PinCode system: 89 | 90 | ======== 91 | ##### Enabling 92 | 93 | ``` 94 | LockManager lockManager = LockManager.getInstance(); 95 | lockManager.enableAppLock(this, CustomPinActivity.class); 96 | ``` 97 | Once enabled, you must extend "PinActivity" for every Activity you wish to protect. 98 | 99 | ======== 100 | ##### Disabling 101 | 102 | ``` 103 | LockManager lockManager = LockManager.getInstance(); 104 | lockManager.disableAppLock(); 105 | ``` 106 | 107 | ======== 108 | #### Set up the PinCode 109 | 110 | Whenever you want the user to set up his pin code, you need to request: 111 | 112 | ``` 113 | Intent intent = new Intent(MainActivity.this, CustomPinActivity.class); 114 | intent.putExtra(AppLock.EXTRA_TYPE, AppLock.ENABLE_PINLOCK); 115 | startActivityForResult(intent, REQUEST_CODE_ENABLE); 116 | ``` 117 | 118 | ======== 119 | #### Unlock system 120 | 121 | As soon as you enable the PinCode system, the Unlock screen will appear by itself when the user resume the app after a defined timeout. 122 | Please refer to the next section to know how to customize these values. 123 | 124 | ======== 125 | ### Customization 126 | 127 | Some features are customizable: 128 | 129 | The unlock timeout: 130 | ------------------- 131 | 132 | ``` 133 | LockManager lockManager = LockManager.getInstance(); 134 | lockManager.getAppLock().setTimeout(10000); 135 | ``` 136 | 137 | The pin length required: 138 | ------------------- 139 | 140 | ``` 141 | public class CustomPinActivity extends AppLockActivity { 142 | 143 | ... 144 | ... 145 | 146 | @Override 147 | public int getPinLength() { 148 | return 5; 149 | } 150 | 151 | ... 152 | ... 153 | } 154 | ``` 155 | 156 | The logo displayed at the top of the page: 157 | ------------------- 158 | 159 | ``` 160 | LockManager lockManager = LockManager.getInstance(); 161 | lockManager.getAppLock().setLogoId(R.drawable.security_lock); 162 | ``` 163 | 164 | The ignored activities: 165 | ------------------- 166 | For instance you got a login activity that you want to avoid getting the lock screen, you can ignore this activity by doing: 167 | 168 | ``` 169 | LockManager lockManager = LockManager.getInstance(); 170 | lockManager.getAppLock().addIgnoredActivity(NotLockedActivity.class); 171 | ``` 172 | 173 | The AppLockActivity Layout: 174 | ------------------- 175 | By providing a custom layout to getContentView() you can alter how your AppLockActivity looks. 176 | However, you must include 4 required elements: 177 | - TextView with an id of pin_code_step_textview 178 | - TextView with an id of pin_code_forgot_textview 179 | - PinCodeRoundView with an id of pin_code_round_view 180 | - KeyboardView with an id of pin_code_keyboard_view 181 | 182 | ``` 183 | @Override 184 | public int getContentView() { 185 | return R.layout.activity_pin; 186 | } 187 | ``` 188 | 189 | The Pin Dots: 190 | ------------------- 191 | By supplying alternate drawable resources for app:lp_empty_pin_dot and app:lp_full_pin_dot you can custimize how it looks. 192 | 193 | ``` 194 | 202 | ``` 203 | 204 | ======== 205 | 206 | ### Credits 207 | 208 | * We used the RippleEffect library from Traex (https://github.com/traex/RippleEffect) to implement the Ripple effect from material design on API 10+ 209 | * We used the L-dialogs library from lewisjdeane (https://github.com/lewisjdeane/L-Dialogs) to demonstrate how to use a popup for the "forgot" button 210 | * We used the Robotium library from RobotiumTech (https://github.com/RobotiumTech/robotium) to run powerful unit and functional testing 211 | 212 | ======== 213 | 214 | ### License 215 | 216 | ``` 217 | The MIT License (MIT) 218 | 219 | Copyright (c) 2015 OrangeGangsters 220 | 221 | Permission is hereby granted, free of charge, to any person obtaining a copy 222 | of this software and associated documentation files (the "Software"), to deal 223 | in the Software without restriction, including without limitation the rights 224 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 225 | copies of the Software, and to permit persons to whom the Software is 226 | furnished to do so, subject to the following conditions: 227 | 228 | The above copyright notice and this permission notice shall be included in all 229 | copies or substantial portions of the Software. 230 | 231 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 232 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 233 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 234 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 235 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 236 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 237 | SOFTWARE. 238 | ``` 239 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion '26.0.2' 6 | 7 | defaultConfig { 8 | applicationId "com.github.orangegangsters.lollipin" 9 | minSdkVersion 14 10 | targetSdkVersion 24 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | 25 | compile project(':lib') 26 | compile 'com.android.support:appcompat-v7:26.0.2' 27 | 28 | //Lollipop dialogs https://github.com/lewisjdeane/L-Dialogs and buttons, animations etc... 29 | compile 'uk.me.lewisdeane.ldialogs:ldialogs:1.2.0@aar' 30 | 31 | //test 32 | androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.5.2' 33 | } 34 | 35 | // REQUIRED: Google's new Maven repo is required for the latest 36 | // support library that is compatible with Android 8.0 37 | repositories { 38 | maven { 39 | url 'https://maven.google.com' 40 | // Alternative URL is 'https://dl.google.com/dl/android/maven2/' 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /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/stoyan/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/lollipin/orangegangsters/github/com/lollipin/functional/AbstractTest.java: -------------------------------------------------------------------------------- 1 | package lollipin.orangegangsters.github.com.lollipin.functional; 2 | 3 | import android.content.SharedPreferences; 4 | import android.preference.PreferenceManager; 5 | import android.test.ActivityInstrumentationTestCase2; 6 | import android.view.View; 7 | 8 | import com.github.omadahealth.lollipin.MainActivity; 9 | import com.github.omadahealth.lollipin.lib.managers.LockManager; 10 | import com.robotium.solo.Solo; 11 | 12 | /** 13 | * Created by stoyan and oliviergoutay on 1/13/15. 14 | */ 15 | public class AbstractTest extends ActivityInstrumentationTestCase2 { 16 | 17 | protected static final String PASSWORD_PREFERENCE_KEY = "PASSCODE"; 18 | protected static final String PASSWORD_ALGORITHM_PREFERENCE_KEY = "ALGORITHM"; 19 | private static final String LAST_ACTIVE_MILLIS_PREFERENCE_KEY = "LAST_ACTIVE_MILLIS"; 20 | protected static final String ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY = "ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY"; 21 | 22 | protected Solo solo; 23 | 24 | public AbstractTest() { 25 | super(MainActivity.class); 26 | } 27 | 28 | public void setUp() throws Exception { 29 | solo = new Solo(getInstrumentation(), getActivity()); 30 | } 31 | 32 | 33 | @Override 34 | public void tearDown() throws Exception { 35 | removeAllPrefs(); 36 | solo.finishOpenedActivities(); 37 | } 38 | 39 | /** 40 | * Click on the specified view id 41 | * 42 | * @param id 43 | */ 44 | protected void clickOnView(int id) { 45 | solo.sleep(300); 46 | solo.clickOnView(getView(id)); 47 | } 48 | 49 | /** 50 | * Get the view for the specified id 51 | * 52 | * @param id 53 | * @return 54 | */ 55 | protected View getView(int id) { 56 | return solo.getView(id); 57 | } 58 | 59 | protected void removeAllPrefs() { 60 | LockManager.getInstance().getAppLock().disableAndRemoveConfiguration(); 61 | } 62 | 63 | protected void setMillis(long millis) { 64 | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity()); 65 | SharedPreferences.Editor editor = sharedPref.edit(); 66 | editor.putLong(LAST_ACTIVE_MILLIS_PREFERENCE_KEY, millis); 67 | editor.apply(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/androidTest/java/lollipin/orangegangsters/github/com/lollipin/functional/PinLockTest.java: -------------------------------------------------------------------------------- 1 | package lollipin.orangegangsters.github.com.lollipin.functional; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.hardware.fingerprint.FingerprintManager; 6 | import android.os.Build; 7 | import android.preference.PreferenceManager; 8 | import android.view.View; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import com.github.omadahealth.lollipin.CustomPinActivity; 13 | import com.github.omadahealth.lollipin.MainActivity; 14 | import com.github.omadahealth.lollipin.NotLockedActivity; 15 | import com.github.omadahealth.lollipin.lib.encryption.Encryptor; 16 | import com.github.omadahealth.lollipin.lib.enums.Algorithm; 17 | import com.github.omadahealth.lollipin.lib.managers.AppLockImpl; 18 | import com.github.omadahealth.lollipin.lib.managers.FingerprintUiHelper; 19 | import com.github.omadahealth.lollipin.lib.managers.LockManager; 20 | import com.github.omadahealth.lollipin.lib.views.PinCodeRoundView; 21 | 22 | import lollipin.orangegangsters.github.com.lollipin.R; 23 | 24 | /** 25 | * @author stoyan and oliviergoutay 26 | * @version 1/13/15 27 | */ 28 | public class PinLockTest extends AbstractTest { 29 | 30 | public void testMigratingFromSha1toSha256() { 31 | //Init 32 | removeAllPrefs(); 33 | AppLockImpl appLockImpl = (AppLockImpl) LockManager.getInstance().getAppLock(); 34 | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity()); 35 | 36 | //Should use sha256 if the SharedPreferences is set, by default 37 | enablePin(); 38 | assertEquals(Algorithm.SHA256, Algorithm.getFromText(sharedPref.getString(PASSWORD_ALGORITHM_PREFERENCE_KEY, ""))); 39 | assertTrue(appLockImpl.checkPasscode("1234")); 40 | removeAllPrefs(); 41 | 42 | //Should still use sha1 if password is stored but not the algorithm 43 | SharedPreferences.Editor editor = sharedPref.edit(); 44 | editor.putString(PASSWORD_PREFERENCE_KEY, Encryptor.getSHA(appLockImpl.getSalt() + "test" + appLockImpl.getSalt(), Algorithm.SHA1)); 45 | editor.apply(); 46 | assertEquals(Algorithm.SHA1, Algorithm.getFromText(sharedPref.getString(PASSWORD_ALGORITHM_PREFERENCE_KEY, ""))); 47 | assertTrue(appLockImpl.checkPasscode("test")); 48 | removeAllPrefs(); 49 | } 50 | 51 | public void testPinClearButton() { 52 | removePrefsAndGoToEnable(); 53 | 54 | //Enter 3 codes 55 | clickOnView(R.id.pin_code_button_1); 56 | clickOnView(R.id.pin_code_button_2); 57 | clickOnView(R.id.pin_code_button_3); 58 | 59 | //Check length 3 60 | solo.sleep(1000); 61 | PinCodeRoundView pinCodeRoundView = (PinCodeRoundView) solo.getCurrentActivity().findViewById(com.github.omadahealth.lollipin.lib.R.id.pin_code_round_view); 62 | assertEquals(3, pinCodeRoundView.getCurrentLength()); 63 | 64 | //Click clear button 65 | clickOnView(R.id.pin_code_button_clear); 66 | 67 | //Check length 0 68 | solo.sleep(1000); 69 | assertEquals(2, pinCodeRoundView.getCurrentLength()); 70 | } 71 | 72 | public void testPinEnabling() { 73 | removePrefsAndGoToEnable(); 74 | 75 | //Test no fingerprint 76 | assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_imageview).getVisibility()); 77 | assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_textview).getVisibility()); 78 | 79 | //--------Not the same pin-------- 80 | //Enter 4 codes 81 | clickOnView(R.id.pin_code_button_1); 82 | clickOnView(R.id.pin_code_button_2); 83 | clickOnView(R.id.pin_code_button_3); 84 | clickOnView(R.id.pin_code_button_4); 85 | solo.sleep(1000); 86 | clickOnView(R.id.pin_code_button_2); 87 | clickOnView(R.id.pin_code_button_3); 88 | clickOnView(R.id.pin_code_button_4); 89 | clickOnView(R.id.pin_code_button_5); 90 | solo.waitForActivity(CustomPinActivity.class); 91 | solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class); 92 | solo.sleep(1000); 93 | 94 | //--------Same pin-------- 95 | enablePin(); 96 | } 97 | 98 | public void testPinEnablingChecking() throws SecurityException { 99 | enablePin(); 100 | 101 | //Go to unlock 102 | clickOnView(R.id.button_unlock_pin); 103 | solo.waitForActivity(CustomPinActivity.class); 104 | solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class); 105 | 106 | //Test fingerprint if available 107 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 108 | ImageView fingerprintImageView = (ImageView) solo.getView(com.github.omadahealth.lollipin.lib.R.id.pin_code_fingerprint_imageview); 109 | TextView fingerprintTextView = (TextView) solo.getView(com.github.omadahealth.lollipin.lib.R.id.pin_code_fingerprint_textview); 110 | FingerprintManager fingerprintManager = (FingerprintManager) getActivity().getSystemService(Context.FINGERPRINT_SERVICE); 111 | FingerprintUiHelper fingerprintUiHelper = new FingerprintUiHelper.FingerprintUiHelperBuilder(fingerprintManager).build(fingerprintImageView, fingerprintTextView, (CustomPinActivity) solo.getCurrentActivity()); 112 | if (fingerprintManager.isHardwareDetected() && fingerprintUiHelper.isFingerprintAuthAvailable()) { 113 | assertEquals(View.VISIBLE, solo.getView(R.id.pin_code_fingerprint_imageview).getVisibility()); 114 | assertEquals(View.VISIBLE, solo.getView(R.id.pin_code_fingerprint_textview).getVisibility()); 115 | } else { 116 | assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_imageview).getVisibility()); 117 | assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_textview).getVisibility()); 118 | } 119 | } else { 120 | assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_imageview).getVisibility()); 121 | assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_textview).getVisibility()); 122 | } 123 | 124 | //Enter the code 125 | clickOnView(R.id.pin_code_button_1); 126 | clickOnView(R.id.pin_code_button_2); 127 | clickOnView(R.id.pin_code_button_3); 128 | clickOnView(R.id.pin_code_button_4); 129 | 130 | //Check view 131 | solo.waitForActivity(MainActivity.class); 132 | solo.assertCurrentActivity("MainActivity", MainActivity.class); 133 | } 134 | 135 | public void testPinEnablingChanging() { 136 | enablePin(); 137 | 138 | //Go to change 139 | clickOnView(R.id.button_change_pin); 140 | solo.waitForActivity(CustomPinActivity.class); 141 | solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class); 142 | 143 | //Enter previous code 144 | clickOnView(R.id.pin_code_button_1); 145 | clickOnView(R.id.pin_code_button_2); 146 | clickOnView(R.id.pin_code_button_3); 147 | clickOnView(R.id.pin_code_button_4); 148 | solo.sleep(1000); 149 | 150 | //Enter the new one 151 | clickOnView(R.id.pin_code_button_2); 152 | clickOnView(R.id.pin_code_button_3); 153 | clickOnView(R.id.pin_code_button_4); 154 | clickOnView(R.id.pin_code_button_5); 155 | solo.sleep(1000); 156 | clickOnView(R.id.pin_code_button_2); 157 | clickOnView(R.id.pin_code_button_3); 158 | clickOnView(R.id.pin_code_button_4); 159 | clickOnView(R.id.pin_code_button_5); 160 | solo.waitForActivity(MainActivity.class); 161 | solo.assertCurrentActivity("MainActivity", MainActivity.class); 162 | 163 | //Go to unlock 164 | clickOnView(R.id.button_unlock_pin); 165 | solo.waitForActivity(CustomPinActivity.class); 166 | solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class); 167 | 168 | //Enter the code 169 | clickOnView(R.id.pin_code_button_2); 170 | clickOnView(R.id.pin_code_button_3); 171 | clickOnView(R.id.pin_code_button_4); 172 | clickOnView(R.id.pin_code_button_5); 173 | 174 | //Check view 175 | solo.waitForActivity(MainActivity.class); 176 | solo.assertCurrentActivity("MainActivity", MainActivity.class); 177 | } 178 | 179 | public void testPinLockAfterDefaultTimeout() { 180 | enablePin(); 181 | 182 | //Go to NotLockedActivity 183 | solo.sleep(1000); 184 | clickOnView(R.id.button_not_locked); 185 | solo.waitForActivity(NotLockedActivity.class); 186 | solo.assertCurrentActivity("NotLockedActivity", NotLockedActivity.class); 187 | 188 | //Set the last time to now - 11sec 189 | setMillis(System.currentTimeMillis() - (1000 * 15)); 190 | solo.getCurrentActivity().finish(); 191 | 192 | //Check view 193 | solo.waitForActivity(CustomPinActivity.class); 194 | solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class); 195 | solo.sleep(1000); 196 | } 197 | 198 | public void testPinLockAfterCustomTimeout() { 199 | enablePin(); 200 | 201 | //Set to 3minutes 202 | LockManager.getInstance().getAppLock().setTimeout(1000 * 60 * 3); 203 | 204 | //Go to NotLockedActivity 205 | clickOnView(R.id.button_not_locked); 206 | solo.waitForActivity(NotLockedActivity.class); 207 | solo.assertCurrentActivity("NotLockedActivity", NotLockedActivity.class); 208 | 209 | //Set the last time to now - 11sec 210 | setMillis(System.currentTimeMillis() - (1000 * 11)); 211 | solo.getCurrentActivity().finish(); 212 | 213 | //Check view 214 | solo.waitForActivity(MainActivity.class); 215 | solo.assertCurrentActivity("MainActivity", MainActivity.class); 216 | solo.sleep(1000); 217 | 218 | //Go to NotLockedActivity 219 | clickOnView(R.id.button_not_locked); 220 | solo.waitForActivity(NotLockedActivity.class); 221 | solo.assertCurrentActivity("NotLockedActivity", NotLockedActivity.class); 222 | 223 | //Set the last time to now - 6minutes 224 | setMillis(System.currentTimeMillis() - (1000 * 60 * 6)); 225 | solo.getCurrentActivity().finish(); 226 | 227 | //Check view 228 | solo.waitForActivity(CustomPinActivity.class); 229 | solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class); 230 | solo.sleep(1000); 231 | } 232 | 233 | public void testPinLockWithBackgroundTimeout() { 234 | enablePin(); 235 | 236 | // Set the option to use timeout in background only 237 | SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity()); 238 | SharedPreferences.Editor editor = sharedPref.edit(); 239 | editor.putBoolean(ONLY_BACKGROUND_TIMEOUT_PREFERENCE_KEY, true); 240 | editor.apply(); 241 | 242 | //Go to NotLockedActivity 243 | solo.sleep(1000); 244 | clickOnView(R.id.button_not_locked); 245 | solo.waitForActivity(NotLockedActivity.class); 246 | solo.assertCurrentActivity("NotLockedActivity", NotLockedActivity.class); 247 | 248 | //Set the last time to now - 15sec 249 | setMillis(System.currentTimeMillis() - (1000 * 15)); 250 | solo.getCurrentActivity().finish(); 251 | 252 | //Check view 253 | solo.waitForActivity(MainActivity.class); 254 | solo.assertCurrentActivity("MainActivity", MainActivity.class); 255 | solo.sleep(1000); 256 | } 257 | 258 | public void testBackButton() { 259 | enablePin(); 260 | 261 | //Go to unlock 262 | clickOnView(R.id.button_unlock_pin); 263 | solo.waitForActivity(CustomPinActivity.class); 264 | solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class); 265 | 266 | solo.goBack(); 267 | solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class); 268 | 269 | //reset 270 | clickOnView(R.id.pin_code_button_1); 271 | clickOnView(R.id.pin_code_button_2); 272 | clickOnView(R.id.pin_code_button_3); 273 | clickOnView(R.id.pin_code_button_4); 274 | solo.sleep(1000); 275 | 276 | //Go to change 277 | clickOnView(R.id.button_change_pin); 278 | solo.waitForActivity(CustomPinActivity.class); 279 | solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class); 280 | 281 | solo.goBack(); 282 | solo.assertCurrentActivity("MainActivity", MainActivity.class); 283 | } 284 | 285 | public void testDisablingFingerprintReader() { 286 | enablePin(); 287 | 288 | // Disable fingerprint reader. 289 | LockManager.getInstance().getAppLock().setFingerprintAuthEnabled(false); 290 | 291 | // Go to unlock. 292 | clickOnView(R.id.button_unlock_pin); 293 | solo.waitForActivity(CustomPinActivity.class); 294 | solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class); 295 | 296 | // Make sure the fingerprint views are gone. 297 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 298 | assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_imageview).getVisibility()); 299 | assertEquals(View.GONE, solo.getView(R.id.pin_code_fingerprint_textview).getVisibility()); 300 | } 301 | 302 | // Make sure pin unlocking still works. 303 | clickOnView(R.id.pin_code_button_1); 304 | clickOnView(R.id.pin_code_button_2); 305 | clickOnView(R.id.pin_code_button_3); 306 | clickOnView(R.id.pin_code_button_4); 307 | solo.waitForActivity(MainActivity.class); 308 | solo.assertCurrentActivity("MainActivity", MainActivity.class); 309 | } 310 | 311 | private void enablePin() { 312 | removePrefsAndGoToEnable(); 313 | 314 | clickOnView(R.id.pin_code_button_1); 315 | clickOnView(R.id.pin_code_button_2); 316 | clickOnView(R.id.pin_code_button_3); 317 | clickOnView(R.id.pin_code_button_4); 318 | solo.sleep(1000); 319 | clickOnView(R.id.pin_code_button_1); 320 | clickOnView(R.id.pin_code_button_2); 321 | clickOnView(R.id.pin_code_button_3); 322 | clickOnView(R.id.pin_code_button_4); 323 | solo.waitForActivity(MainActivity.class); 324 | solo.assertCurrentActivity("MainActivity", MainActivity.class); 325 | } 326 | 327 | private void removePrefsAndGoToEnable() { 328 | //init 329 | removeAllPrefs(); 330 | 331 | //Go to enable 332 | if (solo.getCurrentActivity() instanceof MainActivity) { 333 | clickOnView(R.id.button_enable_pin); 334 | solo.waitForActivity(CustomPinActivity.class); 335 | solo.assertCurrentActivity("CustomPinActivity", CustomPinActivity.class); 336 | solo.waitForText("1"); 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/omadahealth/lollipin/CustomApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.omadahealth.lollipin; 2 | 3 | import android.app.Application; 4 | 5 | import com.github.omadahealth.lollipin.lib.managers.LockManager; 6 | 7 | import lollipin.orangegangsters.github.com.lollipin.R; 8 | 9 | /** 10 | * Created by oliviergoutay on 1/14/15. 11 | */ 12 | public class CustomApplication extends Application { 13 | 14 | @SuppressWarnings("unchecked") 15 | @Override 16 | public void onCreate() { 17 | super.onCreate(); 18 | 19 | LockManager lockManager = LockManager.getInstance(); 20 | lockManager.enableAppLock(this, CustomPinActivity.class); 21 | lockManager.getAppLock().setLogoId(R.drawable.security_lock); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/omadahealth/lollipin/CustomPinActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.omadahealth.lollipin; 2 | 3 | import android.content.res.Resources; 4 | import android.graphics.Color; 5 | import android.graphics.Typeface; 6 | import android.graphics.drawable.ColorDrawable; 7 | import android.widget.Toast; 8 | 9 | import com.github.omadahealth.lollipin.lib.managers.AppLockActivity; 10 | 11 | import lollipin.orangegangsters.github.com.lollipin.R; 12 | import uk.me.lewisdeane.ldialogs.BaseDialog; 13 | import uk.me.lewisdeane.ldialogs.CustomDialog; 14 | 15 | /** 16 | * Created by oliviergoutay on 1/14/15. 17 | */ 18 | public class CustomPinActivity extends AppLockActivity { 19 | 20 | @Override 21 | public void showForgotDialog() { 22 | Resources res = getResources(); 23 | // Create the builder with required paramaters - Context, Title, Positive Text 24 | CustomDialog.Builder builder = new CustomDialog.Builder(this, 25 | res.getString(R.string.activity_dialog_title), 26 | res.getString(R.string.activity_dialog_accept)); 27 | builder.content(res.getString(R.string.activity_dialog_content)); 28 | builder.negativeText(res.getString(R.string.activity_dialog_decline)); 29 | 30 | //Set theme 31 | builder.darkTheme(false); 32 | builder.typeface(Typeface.SANS_SERIF); 33 | builder.positiveColor(res.getColor(R.color.light_blue_500)); // int res, or int colorRes parameter versions available as well. 34 | builder.negativeColor(res.getColor(R.color.light_blue_500)); 35 | builder.rightToLeft(false); // Enables right to left positioning for languages that may require so. 36 | builder.titleAlignment(BaseDialog.Alignment.CENTER); 37 | builder.buttonAlignment(BaseDialog.Alignment.CENTER); 38 | builder.setButtonStacking(false); 39 | 40 | //Set text sizes 41 | builder.titleTextSize((int) res.getDimension(R.dimen.activity_dialog_title_size)); 42 | builder.contentTextSize((int) res.getDimension(R.dimen.activity_dialog_content_size)); 43 | builder.positiveButtonTextSize((int) res.getDimension(R.dimen.activity_dialog_positive_button_size)); 44 | builder.negativeButtonTextSize((int) res.getDimension(R.dimen.activity_dialog_negative_button_size)); 45 | 46 | //Build the dialog. 47 | CustomDialog customDialog = builder.build(); 48 | customDialog.setCanceledOnTouchOutside(false); 49 | customDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 50 | customDialog.setClickListener(new CustomDialog.ClickListener() { 51 | @Override 52 | public void onConfirmClick() { 53 | Toast.makeText(getApplicationContext(), "Yes", Toast.LENGTH_SHORT).show(); 54 | } 55 | 56 | @Override 57 | public void onCancelClick() { 58 | Toast.makeText(getApplicationContext(), "Cancel", Toast.LENGTH_SHORT).show(); 59 | } 60 | }); 61 | 62 | // Show the dialog. 63 | customDialog.show(); 64 | } 65 | 66 | @Override 67 | public void onPinFailure(int attempts) { 68 | 69 | } 70 | 71 | @Override 72 | public void onPinSuccess(int attempts) { 73 | 74 | } 75 | 76 | @Override 77 | public int getPinLength() { 78 | return super.getPinLength();//you can override this method to change the pin length from the default 4 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/omadahealth/lollipin/LockedCompatActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.omadahealth.lollipin; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.widget.Toolbar; 5 | import com.github.omadahealth.lollipin.lib.PinCompatActivity; 6 | import lollipin.orangegangsters.github.com.lollipin.R; 7 | 8 | /** 9 | * Created by callmepeanut on 16-1-14. 10 | */ 11 | public class LockedCompatActivity extends PinCompatActivity{ 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_compat_locked); 17 | initView(); 18 | } 19 | 20 | private void initView() { 21 | Toolbar toolbar = (Toolbar) findViewById(R.id.id_toolbar); 22 | setSupportActionBar(toolbar); 23 | 24 | toolbar.setTitle("Title"); 25 | toolbar.setTitleTextColor(getResources().getColor(android.R.color.white)); 26 | toolbar.setSubtitle("SubTitle"); 27 | toolbar.setSubtitleTextColor(getResources().getColor(android.R.color.white)); 28 | toolbar.setLogo(R.drawable.ic_launcher); 29 | toolbar.setNavigationIcon(R.drawable.ic_menu_white_36dp); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/omadahealth/lollipin/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.omadahealth.lollipin; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Toast; 7 | 8 | import com.github.omadahealth.lollipin.lib.PinActivity; 9 | import com.github.omadahealth.lollipin.lib.managers.AppLock; 10 | 11 | import lollipin.orangegangsters.github.com.lollipin.R; 12 | 13 | 14 | public class MainActivity extends PinActivity implements View.OnClickListener { 15 | 16 | private static final int REQUEST_CODE_ENABLE = 11; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_main); 22 | 23 | this.findViewById(R.id.button_enable_pin).setOnClickListener(this); 24 | this.findViewById(R.id.button_change_pin).setOnClickListener(this); 25 | this.findViewById(R.id.button_unlock_pin).setOnClickListener(this); 26 | this.findViewById(R.id.button_compat_locked).setOnClickListener(this); 27 | this.findViewById(R.id.button_not_locked).setOnClickListener(this); 28 | } 29 | 30 | @Override 31 | public void onClick(View v) { 32 | Intent intent = new Intent(MainActivity.this, CustomPinActivity.class); 33 | switch (v.getId()) { 34 | case R.id.button_enable_pin: 35 | intent.putExtra(AppLock.EXTRA_TYPE, AppLock.ENABLE_PINLOCK); 36 | startActivityForResult(intent, REQUEST_CODE_ENABLE); 37 | break; 38 | case R.id.button_change_pin: 39 | intent.putExtra(AppLock.EXTRA_TYPE, AppLock.CHANGE_PIN); 40 | startActivity(intent); 41 | break; 42 | case R.id.button_unlock_pin: 43 | intent.putExtra(AppLock.EXTRA_TYPE, AppLock.UNLOCK_PIN); 44 | startActivity(intent); 45 | break; 46 | case R.id.button_compat_locked: 47 | Intent intent2 = new Intent(MainActivity.this, LockedCompatActivity.class); 48 | startActivity(intent2); 49 | break; 50 | case R.id.button_not_locked: 51 | Intent intent3 = new Intent(MainActivity.this, NotLockedActivity.class); 52 | startActivity(intent3); 53 | } 54 | } 55 | 56 | @Override 57 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 58 | super.onActivityResult(requestCode, resultCode, data); 59 | 60 | switch (requestCode){ 61 | case REQUEST_CODE_ENABLE: 62 | Toast.makeText(this, "PinCode enabled", Toast.LENGTH_SHORT).show(); 63 | break; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/omadahealth/lollipin/NotLockedActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.omadahealth.lollipin; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | import lollipin.orangegangsters.github.com.lollipin.R; 7 | 8 | /** 9 | * Created by oliviergoutay on 1/13/15. 10 | */ 11 | public class NotLockedActivity extends Activity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_not_locked); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_menu_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omadahealth/LolliPin/80b28d54f3ea88005ee0864a4084ce0484a06171/app/src/main/res/drawable-hdpi/ic_menu_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omadahealth/LolliPin/80b28d54f3ea88005ee0864a4084ce0484a06171/app/src/main/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_menu_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omadahealth/LolliPin/80b28d54f3ea88005ee0864a4084ce0484a06171/app/src/main/res/drawable-mdpi/ic_menu_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omadahealth/LolliPin/80b28d54f3ea88005ee0864a4084ce0484a06171/app/src/main/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_menu_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omadahealth/LolliPin/80b28d54f3ea88005ee0864a4084ce0484a06171/app/src/main/res/drawable-xhdpi/ic_menu_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omadahealth/LolliPin/80b28d54f3ea88005ee0864a4084ce0484a06171/app/src/main/res/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/security_lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omadahealth/LolliPin/80b28d54f3ea88005ee0864a4084ce0484a06171/app/src/main/res/drawable-xhdpi/security_lock.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_menu_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omadahealth/LolliPin/80b28d54f3ea88005ee0864a4084ce0484a06171/app/src/main/res/drawable-xxhdpi/ic_menu_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omadahealth/LolliPin/80b28d54f3ea88005ee0864a4084ce0484a06171/app/src/main/res/drawable-xxhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_menu_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omadahealth/LolliPin/80b28d54f3ea88005ee0864a4084ce0484a06171/app/src/main/res/drawable-xxxhdpi/ic_menu_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_compat_locked.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 |