From b812d10c0fb160624249b3020fdaff5541ad3e6c Mon Sep 17 00:00:00 2001 From: Andy Valdez Date: Mon, 12 Aug 2024 19:03:54 -0400 Subject: [PATCH] [Lib] Migrate passcodelock directly into simplenote as a library module. --- PasscodeLock/.gitignore | 1 + PasscodeLock/build.gradle | 46 ++++ PasscodeLock/consumer-rules.pro | 0 PasscodeLock/gradle.properties | 13 + PasscodeLock/proguard-rules.pro | 21 ++ PasscodeLock/src/main/AndroidManifest.xml | 4 + .../passcodelock/AbstractAppLock.java | 86 +++++++ .../AbstractPasscodeKeyboardActivity.java | 188 ++++++++++++++ .../passcodelock/AppLockManager.java | 60 +++++ .../passcodelock/DefaultAppLock.java | 229 ++++++++++++++++++ .../PasscodeManagePasswordActivity.java | 110 +++++++++ .../PasscodePreferenceFragment.java | 130 ++++++++++ .../PasscodePreferenceFragmentCompat.java | 105 ++++++++ .../passcodelock/PasscodeUnlockActivity.java | 67 +++++ .../wordpress/passcodelock/StringUtils.java | 26 ++ PasscodeLock/src/main/res/anim/cycle_5.xml | 17 ++ PasscodeLock/src/main/res/anim/do_nothing.xml | 5 + PasscodeLock/src/main/res/anim/shake.xml | 20 ++ PasscodeLock/src/main/res/anim/slide_up.xml | 6 + .../drawable-hdpi/ic_backspace_white_24dp.png | Bin 0 -> 905 bytes .../ic_fingerprint_white_24dp.png | Bin 0 -> 1990 bytes .../drawable-mdpi/ic_backspace_white_24dp.png | Bin 0 -> 514 bytes .../ic_fingerprint_white_24dp.png | Bin 0 -> 1020 bytes .../ic_backspace_white_24dp.png | Bin 0 -> 1098 bytes .../ic_fingerprint_white_24dp.png | Bin 0 -> 2679 bytes .../ic_backspace_white_24dp.png | Bin 0 -> 2046 bytes .../ic_fingerprint_white_24dp.png | Bin 0 -> 5098 bytes .../ic_backspace_white_24dp.png | Bin 0 -> 2327 bytes .../ic_fingerprint_white_24dp.png | Bin 0 -> 6003 bytes .../layout-sw600dp/app_passcode_keyboard.xml | 172 +++++++++++++ .../main/res/layout/app_passcode_keyboard.xml | 172 +++++++++++++ .../src/main/res/values-sw600dp/bools.xml | 6 + .../src/main/res/values-xlarge/bools.xml | 6 + PasscodeLock/src/main/res/values/bools.xml | 6 + PasscodeLock/src/main/res/values/colors.xml | 19 ++ PasscodeLock/src/main/res/values/dimens.xml | 9 + PasscodeLock/src/main/res/values/strings.xml | 36 +++ PasscodeLock/src/main/res/values/styles.xml | 31 +++ .../src/main/res/xml/passcode_preferences.xml | 16 ++ Simplenote/build.gradle | 2 +- build.gradle | 7 + settings.gradle | 1 + 42 files changed, 1616 insertions(+), 1 deletion(-) create mode 100644 PasscodeLock/.gitignore create mode 100644 PasscodeLock/build.gradle create mode 100644 PasscodeLock/consumer-rules.pro create mode 100644 PasscodeLock/gradle.properties create mode 100644 PasscodeLock/proguard-rules.pro create mode 100644 PasscodeLock/src/main/AndroidManifest.xml create mode 100644 PasscodeLock/src/main/java/org/wordpress/passcodelock/AbstractAppLock.java create mode 100644 PasscodeLock/src/main/java/org/wordpress/passcodelock/AbstractPasscodeKeyboardActivity.java create mode 100644 PasscodeLock/src/main/java/org/wordpress/passcodelock/AppLockManager.java create mode 100644 PasscodeLock/src/main/java/org/wordpress/passcodelock/DefaultAppLock.java create mode 100644 PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodeManagePasswordActivity.java create mode 100644 PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodePreferenceFragment.java create mode 100644 PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodePreferenceFragmentCompat.java create mode 100644 PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodeUnlockActivity.java create mode 100644 PasscodeLock/src/main/java/org/wordpress/passcodelock/StringUtils.java create mode 100644 PasscodeLock/src/main/res/anim/cycle_5.xml create mode 100644 PasscodeLock/src/main/res/anim/do_nothing.xml create mode 100644 PasscodeLock/src/main/res/anim/shake.xml create mode 100644 PasscodeLock/src/main/res/anim/slide_up.xml create mode 100755 PasscodeLock/src/main/res/drawable-hdpi/ic_backspace_white_24dp.png create mode 100755 PasscodeLock/src/main/res/drawable-hdpi/ic_fingerprint_white_24dp.png create mode 100755 PasscodeLock/src/main/res/drawable-mdpi/ic_backspace_white_24dp.png create mode 100755 PasscodeLock/src/main/res/drawable-mdpi/ic_fingerprint_white_24dp.png create mode 100755 PasscodeLock/src/main/res/drawable-xhdpi/ic_backspace_white_24dp.png create mode 100755 PasscodeLock/src/main/res/drawable-xhdpi/ic_fingerprint_white_24dp.png create mode 100755 PasscodeLock/src/main/res/drawable-xxhdpi/ic_backspace_white_24dp.png create mode 100755 PasscodeLock/src/main/res/drawable-xxhdpi/ic_fingerprint_white_24dp.png create mode 100755 PasscodeLock/src/main/res/drawable-xxxhdpi/ic_backspace_white_24dp.png create mode 100755 PasscodeLock/src/main/res/drawable-xxxhdpi/ic_fingerprint_white_24dp.png create mode 100644 PasscodeLock/src/main/res/layout-sw600dp/app_passcode_keyboard.xml create mode 100644 PasscodeLock/src/main/res/layout/app_passcode_keyboard.xml create mode 100644 PasscodeLock/src/main/res/values-sw600dp/bools.xml create mode 100644 PasscodeLock/src/main/res/values-xlarge/bools.xml create mode 100644 PasscodeLock/src/main/res/values/bools.xml create mode 100644 PasscodeLock/src/main/res/values/colors.xml create mode 100644 PasscodeLock/src/main/res/values/dimens.xml create mode 100644 PasscodeLock/src/main/res/values/strings.xml create mode 100644 PasscodeLock/src/main/res/values/styles.xml create mode 100644 PasscodeLock/src/main/res/xml/passcode_preferences.xml diff --git a/PasscodeLock/.gitignore b/PasscodeLock/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/PasscodeLock/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/PasscodeLock/build.gradle b/PasscodeLock/build.gradle new file mode 100644 index 000000000..3c3dc2d70 --- /dev/null +++ b/PasscodeLock/build.gradle @@ -0,0 +1,46 @@ +buildscript { + repositories { + google() + mavenCentral() + } + +} + +plugins { + id 'com.android.library' +} + +android { + namespace 'org.wordpress.passcodelock' + compileSdk 34 + + defaultConfig { + minSdk 23 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.preference:preference:1.0.0' +} + +android.buildTypes.all { buildType -> + project.properties.any { property -> + if (property.key.toLowerCase().startsWith("passcodelock.")) { + buildType.buildConfigField "String", property.key.replace("passcodelock.", "").replace(".", "_").toUpperCase(), "\"${property.value}\"" + } + } +} diff --git a/PasscodeLock/consumer-rules.pro b/PasscodeLock/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/PasscodeLock/gradle.properties b/PasscodeLock/gradle.properties new file mode 100644 index 000000000..4b0a23828 --- /dev/null +++ b/PasscodeLock/gradle.properties @@ -0,0 +1,13 @@ +android.enableJetifier=true +android.useAndroidX=true + +passcodelock.password_preference_key=passcode_lock_prefs_password_key +passcodelock.password_enc_secret=5-maggio-2002-Karel-Poborsky +passcodelock.fingerprint_enabled_key=passcode_lock_prefs_fingerprint_enabled_key + +ossrhUsername=hello +ossrhPassword=world + +signing.keyId=byebye +signing.password=secret +signing.secretKeyRingFile=/home/user/secret.gpg diff --git a/PasscodeLock/proguard-rules.pro b/PasscodeLock/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/PasscodeLock/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/PasscodeLock/src/main/AndroidManifest.xml b/PasscodeLock/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8bdb7e14b --- /dev/null +++ b/PasscodeLock/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/PasscodeLock/src/main/java/org/wordpress/passcodelock/AbstractAppLock.java b/PasscodeLock/src/main/java/org/wordpress/passcodelock/AbstractAppLock.java new file mode 100644 index 000000000..dd9f660f2 --- /dev/null +++ b/PasscodeLock/src/main/java/org/wordpress/passcodelock/AbstractAppLock.java @@ -0,0 +1,86 @@ +package org.wordpress.passcodelock; + +import android.annotation.TargetApi; +import android.app.Application; +import android.os.Build; + +/** + * Interface for AppLock implementations. + * + * There are situations where the AppLock should not be required within an app. Methods for tracking + * exempt {@link android.app.Activity}'s are provided and sub-class implementations are expected to + * comply with requested exemptions. + * @see #isExemptActivity(String) + * @see #setExemptActivities(String[]) + * @see #getExemptActivities() + * + * Applications can request a one-time delay in locking the app. This can be useful for activities + * that launch external applications with the expectation that the user will return to the calling + * application shortly. + */ +@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) +public abstract class AbstractAppLock implements Application.ActivityLifecycleCallbacks { + public static final String FINGERPRINT_VERIFICATION_BYPASS = "fingerprint-bypass__"; + public static final int DEFAULT_TIMEOUT_S = 2; + public static final int EXTENDED_TIMEOUT_S = 60; + + private int mLockTimeout = DEFAULT_TIMEOUT_S; + private String[] mExemptActivities; + + public boolean isExemptActivity(String name) { + if (name == null) return false; + for (String activityName : getExemptActivities()) { + if (name.equals(activityName)) return true; + } + return false; + } + + public void setExemptActivities(String[] exemptActivities) { + mExemptActivities = exemptActivities; + } + + public String[] getExemptActivities() { + if (mExemptActivities == null) setExemptActivities(new String[0]); + return mExemptActivities; + } + + public void setOneTimeTimeout(int timeout) { + mLockTimeout = timeout; + } + + public int getTimeout() { + return mLockTimeout; + } + + protected boolean isFingerprintPassword(String password) { + return FINGERPRINT_VERIFICATION_BYPASS.equals(password); + } + + /** + * Whether the fingerprint unlocking should be available as option in the unlock screen. + * Default is true, but implementation can override this and make their choice. + * + * Note that this doesn't affect system setting, the device must already have fingerprint unlock + * available and correctly working. + * + * @return true if fingerprint unlock should be enabled on the lock screen + */ + public boolean isFingerprintEnabled() { + return true; + } + + // Stub methods to avoid sub-classes to override to many unused methods. + public boolean enableFingerprint() { + return true; + } + public boolean disableFingerprint() { + return false; + } + + public abstract void enable(); + public abstract void disable(); + public abstract void forcePasswordLock(); + public abstract boolean verifyPassword(String password); + public abstract boolean isPasswordLocked(); + public abstract boolean setPassword(String password); +} diff --git a/PasscodeLock/src/main/java/org/wordpress/passcodelock/AbstractPasscodeKeyboardActivity.java b/PasscodeLock/src/main/java/org/wordpress/passcodelock/AbstractPasscodeKeyboardActivity.java new file mode 100644 index 000000000..5e37cc32c --- /dev/null +++ b/PasscodeLock/src/main/java/org/wordpress/passcodelock/AbstractPasscodeKeyboardActivity.java @@ -0,0 +1,188 @@ +package org.wordpress.passcodelock; + +import android.app.Activity; +import android.content.pm.ActivityInfo; +import android.os.Bundle; +import android.text.InputFilter; +import android.text.Spanned; +import android.view.HapticFeedbackConstants; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.core.hardware.fingerprint.FingerprintManagerCompat; +import androidx.core.os.CancellationSignal; + +public abstract class AbstractPasscodeKeyboardActivity extends Activity { + public static final String KEY_MESSAGE = "message"; + + protected EditText mPinCodeField; + protected InputFilter[] filters = null; + protected TextView topMessage = null; + + protected FingerprintManagerCompat mFingerprintManager; + protected CancellationSignal mCancel; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (!getResources().getBoolean(R.bool.allow_rotation)) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + + setContentView(R.layout.app_passcode_keyboard); + + topMessage = (TextView) findViewById(R.id.passcodelock_prompt); + + Bundle extras = getIntent().getExtras(); + if (extras != null) { + String message = extras.getString(KEY_MESSAGE); + if (message != null) { + topMessage.setText(message); + } + } + + filters = new InputFilter[2]; + filters[0]= new InputFilter.LengthFilter(1); + filters[1] = onlyNumber; + + mPinCodeField = (EditText)findViewById(R.id.pin_field); + + //setup the keyboard + findViewById(R.id.button0).setOnClickListener(defaultButtonListener); + findViewById(R.id.button1).setOnClickListener(defaultButtonListener); + findViewById(R.id.button2).setOnClickListener(defaultButtonListener); + findViewById(R.id.button3).setOnClickListener(defaultButtonListener); + findViewById(R.id.button4).setOnClickListener(defaultButtonListener); + findViewById(R.id.button5).setOnClickListener(defaultButtonListener); + findViewById(R.id.button6).setOnClickListener(defaultButtonListener); + findViewById(R.id.button7).setOnClickListener(defaultButtonListener); + findViewById(R.id.button8).setOnClickListener(defaultButtonListener); + findViewById(R.id.button9).setOnClickListener(defaultButtonListener); + findViewById(R.id.button_erase).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View arg0) { + if (arg0.isHapticFeedbackEnabled()) { + arg0.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); + } + + String curText = mPinCodeField.getText().toString(); + + if (curText.length() > 0) { + mPinCodeField.setText(curText.substring(0, curText.length() - 1)); + mPinCodeField.setSelection(mPinCodeField.length()); + } + } + }); + + mFingerprintManager = FingerprintManagerCompat.from(this); + } + + @Override + public void onPause() { + super.onPause(); + + if (mCancel != null) { + mCancel.cancel(); + } + } + + protected AbstractAppLock getAppLock() { + return AppLockManager.getInstance().getAppLock(); + } + + private OnClickListener defaultButtonListener = new OnClickListener() { + @Override + public void onClick(View arg0) { + int currentValue = -1; + int id = arg0.getId(); + if (id == R.id.button0) { + currentValue = 0; + } else if (id == R.id.button1) { + currentValue = 1; + } else if (id == R.id.button2) { + currentValue = 2; + } else if (id == R.id.button3) { + currentValue = 3; + } else if (id == R.id.button4) { + currentValue = 4; + } else if (id == R.id.button5) { + currentValue = 5; + } else if (id == R.id.button6) { + currentValue = 6; + } else if (id == R.id.button7) { + currentValue = 7; + } else if (id == R.id.button8) { + currentValue = 8; + } else if (id == R.id.button9) { + currentValue = 9; + } + + if (arg0.isHapticFeedbackEnabled()) { + arg0.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); + } + + // Add value and move focus. + mPinCodeField.append(String.valueOf(currentValue)); + mPinCodeField.setSelection(mPinCodeField.length()); + + if (mPinCodeField.length() >= 4) { + onPinLockInserted(); + } + } + }; + + protected void authenticationSucceeded() { + setResult(RESULT_OK); + finish(); + } + + protected void authenticationFailed() { + Thread shake = new Thread() { + public void run() { + Animation shake = AnimationUtils.loadAnimation(AbstractPasscodeKeyboardActivity.this, R.anim.shake); + findViewById(R.id.AppUnlockLinearLayout1).startAnimation(shake); + showPasswordError(); + mPinCodeField.setText(""); + } + }; + runOnUiThread(shake); + } + + protected void showPasswordError(){ + Toast.makeText(AbstractPasscodeKeyboardActivity.this, R.string.passcode_wrong_passcode, Toast.LENGTH_SHORT).show(); + } + + protected abstract void onPinLockInserted(); + protected abstract FingerprintManagerCompat.AuthenticationCallback getFingerprintCallback(); + + private InputFilter onlyNumber = new InputFilter() { + @Override + public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { + if (source.length() > 1) { + return ""; + } + + if (source.length() == 0) { + return null; + } + + try { + int number = Integer.parseInt(source.toString()); + if (number >= 0 && number <= 9) { + return String.valueOf(number); + } + + return ""; + } catch (NumberFormatException e) { + return ""; + } + } + }; +} diff --git a/PasscodeLock/src/main/java/org/wordpress/passcodelock/AppLockManager.java b/PasscodeLock/src/main/java/org/wordpress/passcodelock/AppLockManager.java new file mode 100644 index 000000000..5da851b34 --- /dev/null +++ b/PasscodeLock/src/main/java/org/wordpress/passcodelock/AppLockManager.java @@ -0,0 +1,60 @@ +package org.wordpress.passcodelock; + +import android.app.Application; + +public class AppLockManager { + private static AppLockManager instance; + private AbstractAppLock currentAppLocker; + + public static AppLockManager getInstance() { + if (instance == null) { + instance = new AppLockManager(); + } + return instance; + } + + public void enableDefaultAppLockIfAvailable(Application currentApp) { + if (!DefaultAppLock.isSupportedApi()) return; + + if (currentAppLocker != null) { + if (currentAppLocker instanceof DefaultAppLock) { + // A previous default applocker is already in place + // No need to re-enable it + return; + } + // A previous NON-default applockr is in place. Disable it. + currentAppLocker.disable(); + } + + currentAppLocker = new DefaultAppLock(currentApp); + currentAppLocker.enable(); + } + + public boolean isDefaultLock() { + return getAppLock() != null && getAppLock() instanceof DefaultAppLock; + } + + /** + * @return true when an App lock is available. It could be either a the Default App lock on + * Android-v14 or higher, or a non default App lock + */ + public boolean isAppLockFeatureEnabled() { + return getAppLock() != null && (!isDefaultLock() || DefaultAppLock.isSupportedApi()); + } + + public void setCurrentAppLock(AbstractAppLock newAppLocker) { + if( currentAppLocker != null ) { + currentAppLocker.disable(); //disable the old applocker if available + } + currentAppLocker = newAppLocker; + } + + public AbstractAppLock getAppLock() { + return currentAppLocker; + } + + public void setExtendedTimeout(){ + if (getAppLock() == null) return; + getAppLock().setOneTimeTimeout(AbstractAppLock.EXTENDED_TIMEOUT_S); + } +} diff --git a/PasscodeLock/src/main/java/org/wordpress/passcodelock/DefaultAppLock.java b/PasscodeLock/src/main/java/org/wordpress/passcodelock/DefaultAppLock.java new file mode 100644 index 000000000..e40db5faa --- /dev/null +++ b/PasscodeLock/src/main/java/org/wordpress/passcodelock/DefaultAppLock.java @@ -0,0 +1,229 @@ +package org.wordpress.passcodelock; + +import java.util.Date; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; + +import android.app.Activity; +import android.app.Application; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Build; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.TextUtils; +import android.util.Base64; + +public class DefaultAppLock extends AbstractAppLock { + public static boolean isSupportedApi() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; + } + + private static final String UNLOCK_CLASS_NAME = PasscodeUnlockActivity.class.getName(); + private static final String OLD_PASSWORD_SALT = "sadasauidhsuyeuihdahdiauhs"; + private static final String OLD_APP_LOCK_PASSWORD_PREF_KEY = "wp_app_lock_password_key"; + + private Application mCurrentApp; + private SharedPreferences mSharedPreferences; + private Date mLostFocusDate; + + public DefaultAppLock(Application app) { + super(); + mCurrentApp = app; + mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mCurrentApp); + } + + /** {@link PasscodeUnlockActivity} is always exempt. */ + @Override + public boolean isExemptActivity(String activityName) { + return UNLOCK_CLASS_NAME.equals(activityName) || super.isExemptActivity(activityName); + } + + @Override + public void onActivityPaused(Activity activity) { + if (!isExemptActivity(activity.getClass().getName())) mLostFocusDate = new Date(); + } + + @Override + public void onActivityResumed(Activity activity) { + if (!isExemptActivity(activity.getClass().getName()) && shouldShowUnlockScreen()) { + Intent i = new Intent(activity.getApplicationContext(), PasscodeUnlockActivity.class); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + activity.getApplication().startActivity(i); + } + } + + @Override public void onActivityCreated(Activity arg0, Bundle arg1) {} + @Override public void onActivityDestroyed(Activity arg0) {} + @Override public void onActivitySaveInstanceState(Activity arg0, Bundle arg1) {} + @Override public void onActivityStarted(Activity arg0) {} + @Override public void onActivityStopped(Activity arg0) {} + + public void enable() { + if (!isPasswordLocked()) return; + if (isSupportedApi()) { + mCurrentApp.unregisterActivityLifecycleCallbacks(this); + mCurrentApp.registerActivityLifecycleCallbacks(this); + } + } + + public void disable() { + if (isSupportedApi()) { + mCurrentApp.unregisterActivityLifecycleCallbacks(this); + } + } + + public boolean isPasswordLocked() { + return mSharedPreferences.contains(BuildConfig.PASSWORD_PREFERENCE_KEY) || + mSharedPreferences.contains(OLD_APP_LOCK_PASSWORD_PREF_KEY); + } + + public boolean setPassword(String password) { + removePasswordFromPreferences(); + if (TextUtils.isEmpty(password)) { + disable(); + } else { + savePasswordToPreferences(password.hashCode()); + enable(); + } + return true; + } + + @Override + public boolean isFingerprintEnabled() { + return mSharedPreferences.getBoolean(BuildConfig.FINGERPRINT_ENABLED_KEY, true); + } + + @Override + public boolean enableFingerprint() { + mSharedPreferences.edit().putBoolean(BuildConfig.FINGERPRINT_ENABLED_KEY, true).apply(); + return true; + } + + @Override + public boolean disableFingerprint() { + mSharedPreferences.edit().putBoolean(BuildConfig.FINGERPRINT_ENABLED_KEY, false).apply(); + return true; + } + + public void forcePasswordLock() { + mLostFocusDate = null; + } + + public boolean verifyPassword(String password) { + if (TextUtils.isEmpty(password)) return false; + + // successful fingerprint scan bypasses PIN security + if (isFingerprintPassword(password)) { + mLostFocusDate = new Date(); + return true; + } + + String storedPassword = ""; + String securePassword = null; + int updatedHash = -1; + + if (mSharedPreferences.contains(OLD_APP_LOCK_PASSWORD_PREF_KEY)) { + // backwards compatibility + storedPassword = getStoredLegacyPassword(OLD_APP_LOCK_PASSWORD_PREF_KEY); + securePassword = legacyPasswordHash(password); + } else if (mSharedPreferences.contains(BuildConfig.PASSWORD_PREFERENCE_KEY)) { + if (shouldUpdatePassword()) { + storedPassword = getStoredLegacyPassword(BuildConfig.PASSWORD_PREFERENCE_KEY); + storedPassword = decryptPassword(storedPassword); + storedPassword = stripSalt(storedPassword); + securePassword = password; + updatedHash = password.hashCode(); + } else { + int storedHash = getStoredPassword(); + storedPassword = String.valueOf(storedHash); + securePassword = String.valueOf(password.hashCode()); + } + } + + if (!storedPassword.equalsIgnoreCase(securePassword)) return false; + + // password security updated, replace stored password with integer hash value + if (updatedHash != -1) { + removePasswordFromPreferences(); + savePasswordToPreferences(updatedHash); + } + mLostFocusDate = new Date(); + return true; + } + + private String stripSalt(String saltedPassword) { + if (TextUtils.isEmpty(saltedPassword) || saltedPassword.length() < 4) return ""; + int middle = saltedPassword.length() / 2; + return saltedPassword.substring(middle - 2, middle + 2); + } + + /** Show the unlock screen if there is a saved password and the timeout period has elapsed. */ + private boolean shouldShowUnlockScreen() { + if(!isPasswordLocked()) return false; + if(mLostFocusDate == null) return true; + + int currentTimeOut = getTimeout(); + setOneTimeTimeout(DEFAULT_TIMEOUT_S); + + if (timeSinceLocked() < currentTimeOut) return false; + mLostFocusDate = null; + return true; + } + + private int getStoredPassword() { + return mSharedPreferences.getInt(BuildConfig.PASSWORD_PREFERENCE_KEY, -1); + } + + private void savePasswordToPreferences(int password) { + mSharedPreferences.edit().putInt(BuildConfig.PASSWORD_PREFERENCE_KEY, password).apply(); + } + + private void removePasswordFromPreferences() { + mSharedPreferences.edit() + .remove(OLD_APP_LOCK_PASSWORD_PREF_KEY) + .remove(BuildConfig.PASSWORD_PREFERENCE_KEY) + .apply(); + } + + private int timeSinceLocked() { + return Math.abs((int) ((new Date().getTime() - mLostFocusDate.getTime()) / 1000)); + } + + // + // Legacy methods for backwards compatibility of passwords stored using deprecated security + // + + /** Update to hash-based security if password was stored using encryption-based security. */ + private boolean shouldUpdatePassword() { + Object storedValue = mSharedPreferences.getAll().get(BuildConfig.PASSWORD_PREFERENCE_KEY); + return storedValue != null && storedValue instanceof String; + } + + private String getStoredLegacyPassword(String key) { + return mSharedPreferences.getString(key, ""); + } + + private String legacyPasswordHash(String rawPassword) { + return StringUtils.getMd5Hash(OLD_PASSWORD_SALT + rawPassword + OLD_PASSWORD_SALT); + } + + private String decryptPassword(String encryptedPwd) { + try { + DESKeySpec keySpec = new DESKeySpec(BuildConfig.PASSWORD_ENC_SECRET.getBytes("UTF-8")); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + SecretKey key = keyFactory.generateSecret(keySpec); + + byte[] encryptedWithoutB64 = Base64.decode(encryptedPwd, Base64.DEFAULT); + Cipher cipher = Cipher.getInstance("DES"); + cipher.init(Cipher.DECRYPT_MODE, key); + byte[] plainTextPwdBytes = cipher.doFinal(encryptedWithoutB64); + return new String(plainTextPwdBytes); + } catch (Exception e) { + } + return encryptedPwd; + } +} diff --git a/PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodeManagePasswordActivity.java b/PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodeManagePasswordActivity.java new file mode 100644 index 000000000..f8d5485c2 --- /dev/null +++ b/PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodeManagePasswordActivity.java @@ -0,0 +1,110 @@ +package org.wordpress.passcodelock; + +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +import androidx.core.hardware.fingerprint.FingerprintManagerCompat; +import androidx.core.os.CancellationSignal; + +public class PasscodeManagePasswordActivity extends AbstractPasscodeKeyboardActivity { + public static final String KEY_TYPE = "type"; + + private int type = -1; + private String unverifiedPasscode = null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle extras = getIntent().getExtras(); + if (extras != null) { + type = extras.getInt(KEY_TYPE, -1); + } + } + + @Override + public void onResume() { + super.onResume(); + + // Show fingerprint scanner if supported + if (mFingerprintManager.isHardwareDetected() && + mFingerprintManager.hasEnrolledFingerprints() && + type == PasscodePreferenceFragment.DISABLE_PASSLOCK) { + mFingerprintManager.authenticate(null, 0, mCancel = new CancellationSignal(), getFingerprintCallback(), null); + View view = findViewById(R.id.image_fingerprint); + view.setVisibility(View.VISIBLE); + } + } + + @Override + protected void onPinLockInserted() { + String passLock = mPinCodeField.getText().toString(); + mPinCodeField.setText(""); + + switch (type) { + case PasscodePreferenceFragment.DISABLE_PASSLOCK: + if (AppLockManager.getInstance().getAppLock().verifyPassword(passLock)) { + AppLockManager.getInstance().getAppLock().setPassword(null); + authenticationSucceeded(); + } else { + authenticationFailed(); + } + break; + case PasscodePreferenceFragment.ENABLE_PASSLOCK: + if (unverifiedPasscode == null) { + ((TextView) findViewById(R.id.passcodelock_prompt)).setText(R.string.passcode_re_enter_passcode); + unverifiedPasscode = passLock; + } else { + if (passLock.equals(unverifiedPasscode)) { + AppLockManager.getInstance().getAppLock().setPassword(passLock); + authenticationSucceeded(); + } else { + unverifiedPasscode = null; + topMessage.setText(R.string.passcodelock_prompt_message); + authenticationFailed(); + } + } + break; + case PasscodePreferenceFragment.CHANGE_PASSWORD: + //verify old password + if (AppLockManager.getInstance().getAppLock().verifyPassword(passLock)) { + topMessage.setText(R.string.passcodelock_prompt_message); + type = PasscodePreferenceFragment.ENABLE_PASSLOCK; + } else { + authenticationFailed(); + } + break; + default: + break; + } + } + + @Override + protected FingerprintManagerCompat.AuthenticationCallback getFingerprintCallback() { + return new FingerprintManagerCompat.AuthenticationCallback() { + @Override + public void onAuthenticationError(int errMsgId, CharSequence errString) { + super.onAuthenticationError(errMsgId, errString); + } + + @Override + public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { + super.onAuthenticationHelp(helpMsgId, helpString); + } + + @Override + public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) { + super.onAuthenticationSucceeded(result); + AppLockManager.getInstance().getAppLock().setPassword(null); + authenticationSucceeded(); + } + + @Override + public void onAuthenticationFailed() { + super.onAuthenticationFailed(); + authenticationFailed(); + } + }; + } +} diff --git a/PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodePreferenceFragment.java b/PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodePreferenceFragment.java new file mode 100644 index 000000000..2d0ff18b0 --- /dev/null +++ b/PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodePreferenceFragment.java @@ -0,0 +1,130 @@ +package org.wordpress.passcodelock; + +import android.content.Intent; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.preference.SwitchPreference; + +public class PasscodePreferenceFragment extends PreferenceFragment + implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { + public static final String KEY_SHOULD_INFLATE = "should-inflate"; + public static final int ENABLE_PASSLOCK = 0; + public static final int DISABLE_PASSLOCK = 1; + public static final int CHANGE_PASSWORD = 2; + + private Preference mTogglePasscodePreference; + private Preference mChangePasscodePreference; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + + if (args != null && args.getBoolean(KEY_SHOULD_INFLATE, true)) { + addPreferencesFromResource(R.xml.passcode_preferences); + mTogglePasscodePreference = findPreference(getString(R.string.pref_key_passcode_toggle)); + mChangePasscodePreference = findPreference(getString(R.string.pref_key_change_passcode)); + } + + refreshPreferenceState(); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + String preferenceKey = preference.getKey() != null ? preference.getKey() : ""; + + if (preferenceKey.equals(getString(R.string.pref_key_change_passcode))) { + return handleChangePasscodeClick(); + } + + return false; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (newValue == null) return false; + String preferenceKey = preference.getKey() != null ? preference.getKey() : ""; + if (!preferenceKey.equals(getString(R.string.pref_key_passcode_toggle))) { + // Make sure we're updating the correct preference item. + // Actually this check is not even required, since we've one item only that has set the + // OnPreferenceChangeListener. + return false; + } + + Boolean newValueBool = (Boolean) newValue; + boolean oldValue = ((SwitchPreference)mTogglePasscodePreference).isChecked(); + if (newValueBool == oldValue) { + // Already updated. Do not call the setup activity. + // This method get called twice if the click is on the row (not on the toggle visual item) + // on devices Pre-Lollip. + return true; + } + + handlePasscodeToggleClick(); + + return true; + } + + /** + * When the preferences are nested in a parent apps xml layout the inflated preferences will + * need to be set. + */ + public void setPreferences(Preference togglePreference, Preference changePreference) { + mTogglePasscodePreference = togglePreference; + mChangePasscodePreference = changePreference; + + refreshPreferenceState(); + } + + /** + * Called when user requests to turn the passlock on or off. + * + * @return + * always true to indicate that the request was handled + */ + private boolean handlePasscodeToggleClick() { + int type = AppLockManager.getInstance().getAppLock().isPasswordLocked() + ? DISABLE_PASSLOCK : ENABLE_PASSLOCK; + Intent i = new Intent(getActivity(), PasscodeManagePasswordActivity.class); + i.putExtra(PasscodeManagePasswordActivity.KEY_TYPE, type); + startActivityForResult(i, type); + + return true; + } + + /** + * Called when user requests to change passcode. + * + * @return + * always true to indicate that the request was handled + */ + private boolean handleChangePasscodeClick() { + Intent i = new Intent(getActivity(), PasscodeManagePasswordActivity.class); + i.putExtra(PasscodeManagePasswordActivity.KEY_TYPE, CHANGE_PASSWORD); + i.putExtra(AbstractPasscodeKeyboardActivity.KEY_MESSAGE, + getString(R.string.passcode_enter_old_passcode)); + startActivityForResult(i, CHANGE_PASSWORD); + + return true; + } + + /** + * Helper method to setup preference properties + */ + private void refreshPreferenceState() { + if (mTogglePasscodePreference != null && mChangePasscodePreference != null) { + mChangePasscodePreference.setOnPreferenceClickListener(this); + mTogglePasscodePreference.setOnPreferenceChangeListener(this); + + if (AppLockManager.getInstance().getAppLock().isPasswordLocked()) { + mTogglePasscodePreference.setTitle(R.string.passcode_turn_off); + mChangePasscodePreference.setEnabled(true); + } else { + mTogglePasscodePreference.setTitle(R.string.passcode_turn_on); + mChangePasscodePreference.setEnabled(false); + } + } + } +} diff --git a/PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodePreferenceFragmentCompat.java b/PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodePreferenceFragmentCompat.java new file mode 100644 index 000000000..64676e220 --- /dev/null +++ b/PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodePreferenceFragmentCompat.java @@ -0,0 +1,105 @@ +package org.wordpress.passcodelock; + +import android.content.Intent; +import android.os.Bundle; + +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +public class PasscodePreferenceFragmentCompat extends PreferenceFragmentCompat + implements Preference.OnPreferenceClickListener { + public static final String KEY_SHOULD_INFLATE = "should-inflate"; + public static final int ENABLE_PASSLOCK = 0; + public static final int DISABLE_PASSLOCK = 1; + public static final int CHANGE_PASSWORD = 2; + + private Preference mTogglePasscodePreference; + private Preference mChangePasscodePreference; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + Bundle args = getArguments(); + + if (args != null && args.getBoolean(KEY_SHOULD_INFLATE, true)) { + addPreferencesFromResource(R.xml.passcode_preferences); + mTogglePasscodePreference = findPreference(getString(R.string.pref_key_passcode_toggle)); + mChangePasscodePreference = findPreference(getString(R.string.pref_key_change_passcode)); + } + + refreshPreferenceState(); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + String preferenceKey = preference.getKey() != null ? preference.getKey() : ""; + + if (preferenceKey.equals(getString(R.string.pref_key_passcode_toggle))) { + return handlePasscodeToggleClick(); + } else if (preferenceKey.equals(getString(R.string.pref_key_change_passcode))) { + return handleChangePasscodeClick(); + } + + return false; + } + + /** + * When the preferences are nested in a parent apps xml layout the inflated preferences will + * need to be set. + */ + public void setPreferences(Preference togglePreference, Preference changePreference) { + mTogglePasscodePreference = togglePreference; + mChangePasscodePreference = changePreference; + + refreshPreferenceState(); + } + + /** + * Called when user requests to turn the passlock on or off. + * + * @return + * always true to indicate that the request was handled + */ + private boolean handlePasscodeToggleClick() { + int type = AppLockManager.getInstance().getAppLock().isPasswordLocked() + ? DISABLE_PASSLOCK : ENABLE_PASSLOCK; + Intent i = new Intent(getActivity(), PasscodeManagePasswordActivity.class); + i.putExtra(PasscodeManagePasswordActivity.KEY_TYPE, type); + startActivityForResult(i, type); + + return true; + } + + /** + * Called when user requests to change passcode. + * + * @return + * always true to indicate that the request was handled + */ + private boolean handleChangePasscodeClick() { + Intent i = new Intent(getActivity(), PasscodeManagePasswordActivity.class); + i.putExtra(PasscodeManagePasswordActivity.KEY_TYPE, CHANGE_PASSWORD); + i.putExtra(AbstractPasscodeKeyboardActivity.KEY_MESSAGE, + getString(R.string.passcode_enter_old_passcode)); + startActivityForResult(i, CHANGE_PASSWORD); + + return true; + } + + /** + * Helper method to setup preference properties + */ + private void refreshPreferenceState() { + if (mTogglePasscodePreference != null && mChangePasscodePreference != null) { + mTogglePasscodePreference.setOnPreferenceClickListener(this); + mChangePasscodePreference.setOnPreferenceClickListener(this); + + if (AppLockManager.getInstance().getAppLock().isPasswordLocked()) { + mTogglePasscodePreference.setTitle(R.string.passcode_turn_off); + mChangePasscodePreference.setEnabled(true); + } else { + mTogglePasscodePreference.setTitle(R.string.passcode_turn_on); + mChangePasscodePreference.setEnabled(false); + } + } + } +} diff --git a/PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodeUnlockActivity.java b/PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodeUnlockActivity.java new file mode 100644 index 000000000..bc1bc7e2e --- /dev/null +++ b/PasscodeLock/src/main/java/org/wordpress/passcodelock/PasscodeUnlockActivity.java @@ -0,0 +1,67 @@ +package org.wordpress.passcodelock; + +import android.content.Intent; +import android.view.View; + +import androidx.core.hardware.fingerprint.FingerprintManagerCompat; +import androidx.core.os.CancellationSignal; + +public class PasscodeUnlockActivity extends AbstractPasscodeKeyboardActivity { + @Override + public void onResume() { + super.onResume(); + + if (isFingerprintSupportedAndEnabled()) { + mCancel = new CancellationSignal(); + mFingerprintManager.authenticate(null, 0, mCancel, getFingerprintCallback(), null); + View view = findViewById(R.id.image_fingerprint); + view.setVisibility(View.VISIBLE); + } + } + + @Override + public void onBackPressed() { + getAppLock().forcePasswordLock(); + Intent i = new Intent(); + i.setAction(Intent.ACTION_MAIN); + i.addCategory(Intent.CATEGORY_HOME); + startActivity(i); + finish(); + } + + @Override + protected void onPinLockInserted() { + String passLock = mPinCodeField.getText().toString(); + if (getAppLock().verifyPassword(passLock)) { + authenticationSucceeded(); + } else { + authenticationFailed(); + } + } + + @Override + protected FingerprintManagerCompat.AuthenticationCallback getFingerprintCallback() { + return new FingerprintManagerCompat.AuthenticationCallback() { + @Override + public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) { + // without the call to verifyPassword the unlock screen will show multiple times + getAppLock().verifyPassword(AbstractAppLock.FINGERPRINT_VERIFICATION_BYPASS); + authenticationSucceeded(); + } + + @Override + public void onAuthenticationFailed() { + authenticationFailed(); + } + + @Override public void onAuthenticationError(int errMsgId, CharSequence errString) { } + @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { } + }; + } + + private boolean isFingerprintSupportedAndEnabled() { + return mFingerprintManager.isHardwareDetected() && + mFingerprintManager.hasEnrolledFingerprints() && + getAppLock().isFingerprintEnabled(); + } +} diff --git a/PasscodeLock/src/main/java/org/wordpress/passcodelock/StringUtils.java b/PasscodeLock/src/main/java/org/wordpress/passcodelock/StringUtils.java new file mode 100644 index 000000000..83e790532 --- /dev/null +++ b/PasscodeLock/src/main/java/org/wordpress/passcodelock/StringUtils.java @@ -0,0 +1,26 @@ +package org.wordpress.passcodelock; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import android.util.Log; + +public class StringUtils { + public static String getMd5Hash(String input) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] messageDigest = md.digest(input.getBytes()); + BigInteger number = new BigInteger(1, messageDigest); + String md5 = number.toString(16); + + while (md5.length() < 32) + md5 = "0" + md5; + + return md5; + } catch (NoSuchAlgorithmException e) { + Log.e("MD5", e.getLocalizedMessage()); + return null; + } + } +} diff --git a/PasscodeLock/src/main/res/anim/cycle_5.xml b/PasscodeLock/src/main/res/anim/cycle_5.xml new file mode 100644 index 000000000..4dfe175d7 --- /dev/null +++ b/PasscodeLock/src/main/res/anim/cycle_5.xml @@ -0,0 +1,17 @@ + + + + diff --git a/PasscodeLock/src/main/res/anim/do_nothing.xml b/PasscodeLock/src/main/res/anim/do_nothing.xml new file mode 100644 index 000000000..4c8f1d992 --- /dev/null +++ b/PasscodeLock/src/main/res/anim/do_nothing.xml @@ -0,0 +1,5 @@ + + diff --git a/PasscodeLock/src/main/res/anim/shake.xml b/PasscodeLock/src/main/res/anim/shake.xml new file mode 100644 index 000000000..628940fca --- /dev/null +++ b/PasscodeLock/src/main/res/anim/shake.xml @@ -0,0 +1,20 @@ + + + + diff --git a/PasscodeLock/src/main/res/anim/slide_up.xml b/PasscodeLock/src/main/res/anim/slide_up.xml new file mode 100644 index 000000000..7c6821e29 --- /dev/null +++ b/PasscodeLock/src/main/res/anim/slide_up.xml @@ -0,0 +1,6 @@ + + diff --git a/PasscodeLock/src/main/res/drawable-hdpi/ic_backspace_white_24dp.png b/PasscodeLock/src/main/res/drawable-hdpi/ic_backspace_white_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..fac63ffa1af62a8f03a4c11d04b1204945a17b88 GIT binary patch literal 905 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 Y0a6+5L$o`!yZ`_I07*qoM6N<$g2|f4F8}}l literal 0 HcmV?d00001 diff --git a/PasscodeLock/src/main/res/drawable-mdpi/ic_backspace_white_24dp.png b/PasscodeLock/src/main/res/drawable-mdpi/ic_backspace_white_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..7baee212dd679c0e40a34cfabc977a6d4f5597ea GIT binary patch literal 514 zcmV+d0{#7oP)z4GjsQ)p#YSXz0YN@@~*cM1m zAG-(|@OwCd8#sd;k>~~1^b#Cxq^a??M8w+>!DVKtF!7HdGfR>WSb>ufajgTMnLBU+ z(-HBtO`4e-a1rMs;#sTbKS5?L#Z?@~BOHi`pY?HOF2Q{q!fWh`h%e2%^^KHM)fW-_ z1`!;?b$qYo&T`6icz~^VUnD5vyO>0sd~Za2>m$g_YJP=PwbY2Har-JWSK~Q0;vIfq zGhSk<4c<)mSE9_kj8k|L5p|k@m6^-&2}M@*2HSeUdkL;!W<-M8$@XCcB~g925@&}< zR9bK9k*NH>XP`vQIK!4afrrhK%gVLnviR^NHw0~)K5lGUOYGRR(tWtme6q);H?ez8 zn>Il0pv^=?<>F6e=1!cSBWRbJpgo;62d#(GxQ+S0Dw>m)u9e!7qeeeH#${Le=)UY) zB<1y5?6}o^UALs#k2|Pfjasw+a7INN)VDbQ?@3EO=eD; z)l5dtGKrYhX7=HmOV_$zz3=;d@4ueD_xYax^IZS?y07cHMTccuz;3{Pb?p!AQrmwA zz5%`hz6O4gVT>e&uJ zDp)6JqwOtX6IXMr?00IPu;s+y?e$>RJsAg7L84+pLU{sHa-<^uZ^$O=hg zn*pMLnZO9(SKtw#3%EZbHTHwd}eG71V(V0pk@z8sPq_-LsY+ZW)19%xY z1Q;S|gY5%>?mFF;_0cpwo;bIR76qlz3nh)Ke)Wc?_9mYP%&vw-HFp5xffq}XYYJfJ zqVhsu7H|)6e9_Y-DHUv9?M?m^m@a9q?emMav6AN7j;Z30tksg%*iOHm%ONDqvpuy$ zF;v=6?vhE2)uLEfq9kHz)c8h;rVSG|6_-bs}&aka78D z$@!An+fx;6jQcwd9NRlp6MJnRRFWHAe?wDp<}@Gym@vD$MrZ?IK|?)#oZLPrY=_h2 z`|zS6_lc3r<0%yjiSs0IC=JNcIJ{#8{eXnF%;t>EG%zc<6Ux>DlO<(_bX@(8%;w#; qqneGGS*ZX~4*L_{@`+mfn56wA#775Hc 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 literal 0 HcmV?d00001 diff --git a/PasscodeLock/src/main/res/drawable-xxhdpi/ic_backspace_white_24dp.png b/PasscodeLock/src/main/res/drawable-xxhdpi/ic_backspace_white_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..7414d811d1171886f464194029111f344f31e7f3 GIT binary patch literal 2046 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

Uy7I_>8f-lYz-hB5U#*yp52Z#8=^ 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 literal 0 HcmV?d00001 diff --git a/PasscodeLock/src/main/res/drawable-xxxhdpi/ic_fingerprint_white_24dp.png b/PasscodeLock/src/main/res/drawable-xxxhdpi/ic_fingerprint_white_24dp.png new file mode 100755 index 0000000000000000000000000000000000000000..d453c3df931990b866ea438c099ecd5996213d2e GIT binary patch literal 6003 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

i|0Ea%&d^M+W3x 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PasscodeLock/src/main/res/layout/app_passcode_keyboard.xml b/PasscodeLock/src/main/res/layout/app_passcode_keyboard.xml new file mode 100644 index 000000000..cbaed5825 --- /dev/null +++ b/PasscodeLock/src/main/res/layout/app_passcode_keyboard.xml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PasscodeLock/src/main/res/values-sw600dp/bools.xml b/PasscodeLock/src/main/res/values-sw600dp/bools.xml new file mode 100644 index 000000000..6a5d1671a --- /dev/null +++ b/PasscodeLock/src/main/res/values-sw600dp/bools.xml @@ -0,0 +1,6 @@ + + + + true + + diff --git a/PasscodeLock/src/main/res/values-xlarge/bools.xml b/PasscodeLock/src/main/res/values-xlarge/bools.xml new file mode 100644 index 000000000..6a5d1671a --- /dev/null +++ b/PasscodeLock/src/main/res/values-xlarge/bools.xml @@ -0,0 +1,6 @@ + + + + true + + diff --git a/PasscodeLock/src/main/res/values/bools.xml b/PasscodeLock/src/main/res/values/bools.xml new file mode 100644 index 000000000..ba24ca886 --- /dev/null +++ b/PasscodeLock/src/main/res/values/bools.xml @@ -0,0 +1,6 @@ + + + + false + + diff --git a/PasscodeLock/src/main/res/values/colors.xml b/PasscodeLock/src/main/res/values/colors.xml new file mode 100644 index 000000000..57a7ef661 --- /dev/null +++ b/PasscodeLock/src/main/res/values/colors.xml @@ -0,0 +1,19 @@ + + + + + #ff444444 + #ff5f5f5f + #ffcccccc + #ffe5e5e5 + + @color/gray_e5 + @android:color/black + @android:color/black + @color/gray_c + @android:color/white + @color/gray_5f + @color/gray_4 + @android:color/black + + diff --git a/PasscodeLock/src/main/res/values/dimens.xml b/PasscodeLock/src/main/res/values/dimens.xml new file mode 100644 index 000000000..6c02fe81e --- /dev/null +++ b/PasscodeLock/src/main/res/values/dimens.xml @@ -0,0 +1,9 @@ + + + + + 24sp + 1dp + 18dp + + diff --git a/PasscodeLock/src/main/res/values/strings.xml b/PasscodeLock/src/main/res/values/strings.xml new file mode 100644 index 000000000..cb923a2e3 --- /dev/null +++ b/PasscodeLock/src/main/res/values/strings.xml @@ -0,0 +1,36 @@ + + + + + PIN + Manage passcode + Enter your old passcode + Re-enter your passcode + Change passcode + Passcode set + Wrong passcode, please try again. + Passcode lock + Turn passcode off + Turn passcode on + + Enter your passcode + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + + turn_passcode_on_off + change_passcode + + Delete + Fingerprint authentication supported + + diff --git a/PasscodeLock/src/main/res/values/styles.xml b/PasscodeLock/src/main/res/values/styles.xml new file mode 100644 index 000000000..c40ba93e9 --- /dev/null +++ b/PasscodeLock/src/main/res/values/styles.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/PasscodeLock/src/main/res/xml/passcode_preferences.xml b/PasscodeLock/src/main/res/xml/passcode_preferences.xml new file mode 100644 index 000000000..62b443989 --- /dev/null +++ b/PasscodeLock/src/main/res/xml/passcode_preferences.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/Simplenote/build.gradle b/Simplenote/build.gradle index 3534d3ebb..a205d3f55 100644 --- a/Simplenote/build.gradle +++ b/Simplenote/build.gradle @@ -112,7 +112,6 @@ dependencies { // Tracks lib is referring to an unavailable build of okhttp exclude group: 'com.squareup.okhttp3' } - implementation 'org.wordpress:passcodelock:2.0.2' // Kotlin's coroutines implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' @@ -148,6 +147,7 @@ dependencies { kapt 'com.google.dagger:hilt-compiler:2.38.1' wearApp project(':Wear') + implementation project(':PasscodeLock') } repositories { diff --git a/build.gradle b/build.gradle index 4788aab58..a871cbe3b 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,13 @@ buildscript { } } +allprojects { + repositories { + google() + maven { url 'https://maven.google.com' } + } +} + apply plugin: 'com.automattic.android.configure' def gitHash() { diff --git a/settings.gradle b/settings.gradle index f9393f4a7..5cb6c5650 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ include ':Simplenote', ':Wear' +include ':PasscodeLock'