init PIN Lock

This commit is contained in:
aliveoutside
2023-02-20 21:02:28 +03:00
parent b0753eefcf
commit 02e8732e9f
40 changed files with 2592 additions and 16 deletions
@@ -0,0 +1,318 @@
package com.guardanis.applock;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
import androidx.core.os.CancellationSignal;
import com.guardanis.applock.activities.UnlockActivity;
import com.guardanis.applock.services.FingerprintLockService;
import com.guardanis.applock.services.FingerprintLockService.AuthenticationDelegate;
import com.guardanis.applock.services.LockService;
import com.guardanis.applock.services.PINLockService;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import ru.vtosters.lite.utils.AndroidUtils;
public class AppLock {
public static final int REQUEST_CODE_UNLOCK = 9371;
public static final int REQUEST_CODE_LOCK_CREATION = 9372;
public static final int REQUEST_CODE_FINGERPRINT_PERMISSION = 9373;
public static final int ERROR_CODE_FINGERPRINTS_MISSING_HARDWARE = 1;
public static final int ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED = 2;
public static final int ERROR_CODE_FINGERPRINTS_EMPTY = 3;
public static final int ERROR_CODE_FINGERPRINTS_NOT_LOCALLY_ENROLLED = 4;
public static final int ERROR_CODE_SDK_VERSION_MINIMUM = 5;
private static final String PREFS = "pin__preferences";
private static final String PREF_UNLOCK_FAILURE_TIME = "pin__unlock_failure_time";
private static final String PREF_UNLOCK_SUCCESS_TIME = "pin__unlock_success_time";
private static AppLock instance;
protected Context context;
protected HashMap<Class, LockService> lockServices = new HashMap<Class, LockService>();
protected int unlockAttemptsCount = 1;
protected AppLock(Context context) {
this.context = context.getApplicationContext();
this.lockServices.put(PINLockService.class, new PINLockService());
this.lockServices.put(FingerprintLockService.class, new FingerprintLockService());
}
public static AppLock getInstance(Context context) {
if (instance == null)
instance = new AppLock(context);
return instance;
}
/**
* @return true if the user has enrolled in either PIN or Fingerprint locking
*/
public static boolean isEnrolled(Context context) {
AppLock helper = getInstance(context);
for (LockService service : helper.lockServices.values()) {
if (service.isEnrolled(context))
return true;
}
return false;
}
/**
* @return true if the user is enrolled in locking and the last successful unlock happened more than the default lock duration ago
*/
public static boolean isUnlockRequired(Context context) {
int minutes = context.getResources()
.getInteger(AndroidUtils.getIdentifier("applock__activity_lock_reenable_minutes", "integer"));
return isUnlockRequired(context, TimeUnit.MINUTES.toMillis(minutes));
}
/**
* @return true if the user is enrolled in locking and the last successful unlock happened more than lastSuccessValidMs ago
*/
public static boolean isUnlockRequired(Context context, long lastSuccessValidMs) {
return isEnrolled(context) && lastSuccessValidMs < System.currentTimeMillis() - getUnlockSuccessTime(context);
}
protected static long getUnlockSuccessTime(Context context) {
return AppLock.getInstance(context)
.getPreferences()
.getLong(PREF_UNLOCK_SUCCESS_TIME, 0);
}
public static void onActivityResumed(Activity activity) {
if (isUnlockRequired(activity)) {
boolean unlockActivityReturnAllowed = activity.getResources()
.getBoolean(AndroidUtils.getIdentifier("applock__unlock_activity_return_allowed", "bool"));
Intent intent = new Intent(activity, UnlockActivity.class)
.putExtra(UnlockActivity.INTENT_ALLOW_UNLOCKED_EXIT, unlockActivityReturnAllowed);
activity.startActivityForResult(intent, REQUEST_CODE_UNLOCK);
}
}
/**
* Check if an action-based unlock is required and navigates to the UnlockActivity if true.
*
* @return true if unlock is required.
*/
public static boolean unlockIfRequired(Activity activity) {
if (isUnlockRequired(activity)) {
Intent intent = new Intent(activity, UnlockActivity.class)
.putExtra(UnlockActivity.INTENT_ALLOW_UNLOCKED_EXIT, true);
activity.startActivityForResult(intent, REQUEST_CODE_UNLOCK);
return true;
} else
return false;
}
public void attemptFingerprintUnlock(final UnlockDelegate eventListener) {
if (handleFailureBlocking(eventListener))
return;
AuthenticationDelegate delegate = new AuthenticationDelegate() {
@Override
public void onResolutionRequired(int errorCode) {
// eventListener.onResolutionRequired(errorCode);
}
@Override
public void onAuthenticationHelp(int code, CharSequence message) {
eventListener.onAuthenticationHelp(code, String.valueOf(message));
}
@Override
public void onAuthenticating(CancellationSignal cancellationSignal) {
// Handled internally
}
@Override
public void onAuthenticationSuccess(@NonNull FingerprintManagerCompat.AuthenticationResult result) {
onUnlockSuccessful(eventListener);
}
@Override
public void onAuthenticationFailed(String message) {
Log.w(getClass().getSimpleName(), "Fingerprint authentication failed: " + message);
}
};
getLockService(FingerprintLockService.class)
.authenticate(context, delegate);
}
public void attemptPINUnlock(String pin, final UnlockDelegate eventListener) {
if (handleFailureBlocking(eventListener))
return;
PINLockService.AuthenticationDelegate delegate = new PINLockService.AuthenticationDelegate() {
@Override
public void onNoPIN() {
onUnlockFailed(context.getString(AndroidUtils.getIdentifier("applock__unlock_error_no_matching_pin_found", "string")));
}
@Override
public void onPINDoesNotMatch() {
onUnlockFailed(context.getString(AndroidUtils.getIdentifier("applock__unlock_error_match_failed", "string")));
}
@Override
public void onPINMatches() {
onUnlockSuccessful(eventListener);
}
private void onUnlockFailed(String message) {
handleUnlockFailure(message, eventListener);
}
};
getLockService(PINLockService.class)
.authenticate(context, pin, delegate);
}
/**
* @return true if failure blocking is enabled
*/
private boolean handleFailureBlocking(final UnlockDelegate eventListener) {
if (isUnlockFailureBlockEnabled()) {
unlockAttemptsCount++;
if (getFailureDelayMs() < System.currentTimeMillis() - getUnlockFailureBlockStart())
resetUnlockFailure();
else {
String message = String.format(
context.getString(AndroidUtils.getIdentifier("applock__unlock_error_retry_limit_exceeded", "string")),
formatTimeRemaining());
if (eventListener != null)
eventListener.onFailureLimitExceeded(message);
return true;
}
}
return false;
}
protected void handleUnlockFailure(String message, UnlockDelegate eventListener) {
this.unlockAttemptsCount++;
if (eventListener != null)
eventListener.onFailureLimitExceeded(message);
if (context.getResources().getInteger(AndroidUtils.getIdentifier("applock__max_retry_count", "integer")) < unlockAttemptsCount)
onFailureExceedsLimit();
}
public SharedPreferences getPreferences() {
return context.getSharedPreferences(PREFS, 0);
}
protected void onFailureExceedsLimit() {
getPreferences()
.edit()
.putLong(PREF_UNLOCK_FAILURE_TIME, System.currentTimeMillis())
.commit();
}
public boolean isUnlockFailureBlockEnabled() {
return context.getResources().getInteger(AndroidUtils.getIdentifier("applock__max_retry_count", "integer")) < unlockAttemptsCount
|| System.currentTimeMillis() - getUnlockFailureBlockStart() < getFailureDelayMs();
}
protected long getUnlockFailureBlockStart() {
return getPreferences()
.getLong(PREF_UNLOCK_FAILURE_TIME, 0);
}
protected void onUnlockSuccessful(UnlockDelegate eventListener) {
getPreferences()
.edit()
.putLong(PREF_UNLOCK_SUCCESS_TIME, System.currentTimeMillis())
.commit();
resetUnlockFailure();
if (eventListener != null)
eventListener.onUnlockSuccessful();
}
/**
* Reset the last login time to require the user to re-authenticate with
* AppLock the next time they are eligible to unlock.
*/
public void setAuthenticationRequired() {
getPreferences()
.edit()
.putLong(PREF_UNLOCK_SUCCESS_TIME, 0)
.commit();
}
protected void resetUnlockFailure() {
unlockAttemptsCount = 1;
getPreferences()
.edit()
.putLong(PREF_UNLOCK_FAILURE_TIME, 0)
.commit();
}
protected String formatTimeRemaining() {
long millis = getFailureDelayMs() - (System.currentTimeMillis() - getUnlockFailureBlockStart());
long seconds = TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis));
if (TimeUnit.MILLISECONDS.toMinutes(millis) < 1)
return String.format("%d seconds", seconds);
else
return String.format("%d minutes, %d seconds", TimeUnit.MILLISECONDS.toMinutes(millis), seconds);
}
protected long getFailureDelayMs() {
return TimeUnit.MINUTES.toMillis(context.getResources()
.getInteger(AndroidUtils.getIdentifier("applock__failure_retry_delay", "integer")));
}
/**
* This will remove all PIN and/or Fingerprint enrollment data.
* Users will need to re-enroll in AppLock after this call.
*/
public void invalidateEnrollments() {
resetUnlockFailure();
setAuthenticationRequired();
for (LockService service : lockServices.values())
service.invalidateEnrollments(context);
}
public void cancelPendingAuthentications() {
for (LockService service : lockServices.values())
service.cancelPendingAuthentications(context);
}
public <T extends LockService> T getLockService(Class<T> named) {
return (T) lockServices.get(named);
}
public interface UnlockDelegate {
void onUnlockSuccessful();
void onResolutionRequired(int errorCode);
void onAuthenticationHelp(int code, String message);
void onFailureLimitExceeded(String message);
}
}
@@ -0,0 +1,38 @@
package com.guardanis.applock.activities;
import static ru.vtosters.lite.utils.AndroidUtils.getIdentifier;
import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.guardanis.applock.views.LockCreationViewController;
import ru.vtosters.lite.utils.ThemesUtils;
public class LockCreationActivity extends AppCompatActivity implements LockCreationViewController.Delegate {
protected LockCreationViewController viewController;
@Override
public void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(getIdentifier("applock__activity_lock_creation", "layout"));
((LinearLayout) findViewById(getIdentifier("applock__activity_lock_creation", "id"))).setBackgroundColor(ThemesUtils.getBackgroundContent());
((TextView) findViewById(getIdentifier("pin__description", "id"))).setTextColor(ThemesUtils.getTextAttr());
this.viewController = new LockCreationViewController(this, findViewById(getIdentifier("pin__container", "id")));
this.viewController.setDelegate(this);
this.viewController.setupRootFlow();
}
@Override
public void onLockCreated() {
setResult(Activity.RESULT_OK);
finish();
}
}
@@ -0,0 +1,31 @@
package com.guardanis.applock.activities;
import android.app.Activity;
import android.content.Intent;
import androidx.appcompat.app.AppCompatActivity;
import com.guardanis.applock.AppLock;
public class LockableAppCompatActivity extends AppCompatActivity {
@Override
protected void onPostResume() {
super.onPostResume();
AppLock.onActivityResumed(this);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == AppLock.REQUEST_CODE_UNLOCK && resultCode == Activity.RESULT_CANCELED)
handleUnlockActivityReturnFailure();
}
protected void handleUnlockActivityReturnFailure() {
setResult(Activity.RESULT_CANCELED);
finish();
}
}
@@ -0,0 +1,71 @@
package com.guardanis.applock.activities;
import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.guardanis.applock.views.UnlockViewController;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.ThemesUtils;
public class UnlockActivity extends AppCompatActivity implements UnlockViewController.Delegate {
public static final String INTENT_ALLOW_UNLOCKED_EXIT = "pin__allow_activity_exit"; // false by default
protected UnlockViewController viewController;
@Override
public void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(AndroidUtils.getIdentifier("applock__activity_unlock", "layout"));
((LinearLayout) findViewById(AndroidUtils.getIdentifier("applock__activity_unlock", "id"))).setBackgroundColor(ThemesUtils.getBackgroundContent());
((TextView) findViewById(AndroidUtils.getIdentifier("pin__description", "id"))).setTextColor(ThemesUtils.getTextAttr());
this.viewController = new UnlockViewController(this, findViewById(AndroidUtils.getIdentifier("pin__container", "id")));
this.viewController.setAutoAuthorizationEnabled(false); // Disable auto authorization so fingerprint doesn't crash onResume
this.viewController.setDelegate(this);
this.viewController.setupRootFlow();
this.viewController.setAutoAuthorizationEnabled(true);
}
@Override
public void onUnlockSuccessful() {
setResult(Activity.RESULT_OK);
finish();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (event.getRepeatCount() == 0) handleBackPressed();
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public void onBackPressed() {
handleBackPressed();
}
protected void handleBackPressed() {
if (!getIntent().getBooleanExtra(INTENT_ALLOW_UNLOCKED_EXIT, false)) {
Toast.makeText(this, getString(AndroidUtils.getIdentifier("applock__toast_unlock_required", "string")), Toast.LENGTH_LONG).show();
return;
}
setResult(Activity.RESULT_CANCELED);
finish();
}
}
@@ -0,0 +1,107 @@
package com.guardanis.applock.dialogs;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.ContextWrapper;
import android.os.Build;
import android.util.Log;
import android.view.View;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDialog;
import com.guardanis.applock.AppLock;
import com.guardanis.applock.views.AppLockViewController;
import java.lang.ref.WeakReference;
public abstract class AppLockDialogBuilder<ALVC extends AppLockViewController> {
protected WeakReference<Activity> activity;
protected ALVC viewController;
protected WeakReference<AppCompatDialog> dialog = new WeakReference<AppCompatDialog>(null);
protected int layoutResId;
public AppLockDialogBuilder(Activity activity, int layoutResId) {
this.activity = new WeakReference<>(activity);
this.layoutResId = layoutResId;
}
public Dialog show() {
if (viewController != null)
throw new RuntimeException("You can only call show() once per AppLockDialogBuilder instance.");
final Activity activity = this.activity.get();
if (activity == null)
return null;
View parent = activity.getLayoutInflater()
.inflate(layoutResId, null, false);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setView(parent);
builder.setOnCancelListener(dialogInterface -> handleCanceled());
builder.setOnDismissListener(dialogInterface -> handleDismissed());
this.viewController = buildViewControllerInstance(parent);
this.viewController.setupRootFlow();
AppCompatDialog dialog = builder.show();
this.dialog = new WeakReference<>(dialog);
return dialog;
}
protected abstract ALVC buildViewControllerInstance(View parent);
protected void handleCanceled() {
dismissDialog();
}
protected void handleDismissed() {
this.dialog = new WeakReference<>(null);
this.viewController.unregisterReceivers();
Activity activity = this.activity.get();
if (activity == null)
return;
AppLock.getInstance(activity)
.cancelPendingAuthentications();
}
protected void dismissDialog() {
try {
AppCompatDialog dialog = this.dialog.get();
if (dialog == null)
return;
// Attempt to prevent stupid fucking uncatchable system error "View not Attached to Window" when dialog dismissed
Context context = ((ContextWrapper) dialog.getContext()).getBaseContext();
if (context instanceof Activity) {
if (!((Activity) context).isFinishing()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (!((Activity) context).isDestroyed())
dialog.dismiss();
} else
dialog.dismiss();
}
} else
dialog.dismiss();
} catch (Throwable e) {
Log.e("AppLockDialogBuilder", "View not Attached to Window???", e);
}
this.dialog = new WeakReference<>(null);
}
public ALVC getViewController() {
return viewController;
}
}
@@ -0,0 +1,60 @@
package com.guardanis.applock.dialogs;
import android.app.Activity;
import android.view.View;
import com.guardanis.applock.views.LockCreationViewController;
import ru.vtosters.lite.utils.AndroidUtils;
public class LockCreationDialogBuilder extends AppLockDialogBuilder<LockCreationViewController> implements LockCreationViewController.Delegate {
protected Runnable lockCreatedCallback;
protected Runnable canceledCallback;
public LockCreationDialogBuilder(Activity activity) {
super(activity, AndroidUtils.getIdentifier("applock__lock_creation", "layout"));
}
/**
* Set a Runnable to be triggered when a user has successfully enrolled in locking.
*/
public LockCreationDialogBuilder onLockCreated(Runnable lockCreatedCallback) {
this.lockCreatedCallback = lockCreatedCallback;
return this;
}
/**
* Set a Runnable to be triggered when a user has canceled enrollment.
*/
public LockCreationDialogBuilder onCanceled(Runnable canceledCallback) {
this.canceledCallback = canceledCallback;
return this;
}
@Override
protected LockCreationViewController buildViewControllerInstance(View parent) {
LockCreationViewController controller = new LockCreationViewController(activity.get(), parent);
controller.setDelegate(this);
return controller;
}
@Override
public void onLockCreated() {
dismissDialog();
if (lockCreatedCallback != null)
lockCreatedCallback.run();
}
@Override
protected void handleCanceled() {
super.handleCanceled();
if (canceledCallback != null)
canceledCallback.run();
}
}
@@ -0,0 +1,110 @@
package com.guardanis.applock.dialogs;
import android.app.Activity;
import android.app.Dialog;
import android.view.View;
import com.guardanis.applock.AppLock;
import com.guardanis.applock.views.UnlockViewController;
import ru.vtosters.lite.utils.AndroidUtils;
public class UnlockDialogBuilder extends AppLockDialogBuilder<UnlockViewController> implements UnlockViewController.Delegate {
protected Runnable unlockCallback;
protected Runnable canceledCallback;
public UnlockDialogBuilder(Activity activity) {
super(activity, AndroidUtils.getIdentifier("applock__unlock", "layout"));
}
/**
* Set a Runnable to be triggered when a user has successfully unlocked.
*/
public UnlockDialogBuilder onUnlocked(Runnable unlockCallback) {
this.unlockCallback = unlockCallback;
return this;
}
/**
* Set a Runnable to be triggered when a user has canceled unlocking.
*/
public UnlockDialogBuilder onCanceled(Runnable canceledCallback) {
this.canceledCallback = canceledCallback;
return this;
}
@Override
protected UnlockViewController buildViewControllerInstance(View parent) {
UnlockViewController controller = new UnlockViewController(activity.get(), parent);
controller.setDelegate(this);
return controller;
}
@Override
public void onUnlockSuccessful() {
dismissDialog();
triggerUnlockCallback();
}
@Override
protected void handleCanceled() {
super.handleCanceled();
if (canceledCallback != null)
canceledCallback.run();
}
protected void triggerUnlockCallback() {
if (unlockCallback != null)
unlockCallback.run();
}
/**
* Check if the user is actually enrolled in locking before opening the unlock dialog.
* If the user is enrolled, show() will be called. Otherwise, this function will trigger this
* instance's UnlockDialogBuilder.unlockCallback immediately.
*
* @return Dialog if show() is called
*/
public Dialog showIfEnrolledOrSuccess() {
Activity activity = this.activity.get();
if (activity == null)
return null;
if (!AppLock.isEnrolled(activity)) {
triggerUnlockCallback();
return null;
}
return show();
}
/**
* Check if the user is both enrolled in locking and that unlocking is required before
* opening the unlock dialog. If the user is enrolled, show() will be called. Otherwise,
* this function will trigger this instance's UnlockDialogBuilder.unlockCallback immediately.
*
* @param longValidDurationMs the milliseconds a user's last unlock is valid for
* @return Dialog if show() is called
*/
public Dialog showIfRequiredOrSuccess(long longValidDurationMs) {
Activity activity = this.activity.get();
if (activity == null)
return null;
if (!AppLock.isUnlockRequired(activity, longValidDurationMs)) {
triggerUnlockCallback();
return null;
}
return show();
}
}
@@ -0,0 +1,114 @@
package com.guardanis.applock.pin;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import java.lang.ref.WeakReference;
interface InputTextListener {
void onTextEntered(int count);
}
public class PINInputController implements TextView.OnEditorActionListener, InputTextListener {
private final WeakReference<PINInputView> inputView;
private WeakReference<InputEventListener> eventListener = new WeakReference<>(null);
/**
* If you want to actually receive events after this, make sure you call setInputEventListener
*/
public PINInputController(PINInputView inputView) {
this.inputView = new WeakReference<>(inputView);
inputView.setOnEditorActionListener(this);
inputView.setInputTextListener(this);
}
public PINInputController ensureKeyboardVisible() {
final PINInputView inputView = this.inputView.get();
if (inputView == null)
return this;
inputView.postDelayed(inputView::ensureKeyboardVisible, 300);
return this;
}
public PINInputController setPasswordCharactersEnabled(boolean passwordCharacterEnabled) {
this.inputView.get().setPasswordCharactersEnabled(passwordCharacterEnabled);
return this;
}
public PINInputController setInputNumbersCount(int inputNumbersCount) {
this.inputView.get().setInputViewsCount(inputNumbersCount);
return this;
}
public PINInputController setInputEventListener(InputEventListener eventListener) {
this.eventListener = new WeakReference<>(eventListener);
return this;
}
@Override
public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
PINInputView inputView = this.inputView.get();
if (inputView == null)
return false;
if (isSoftKeyboardFinishedAction(textView, i, keyEvent)) {
InputEventListener eventListener = this.eventListener.get();
if (eventListener != null)
eventListener.onInputEntered(inputView.getText());
inputView.reset();
return true;
}
return false;
}
private boolean isSoftKeyboardFinishedAction(TextView view, int action, KeyEvent event) {
// Some devices return null event on editor actions for Enter Button (thanks, Samsung)
if (!(event == null || event.getAction() == KeyEvent.ACTION_DOWN))
return false;
// Enter clicked on Bluetooth Keyboard (action is zero on hardware keyboards)
if (action == EditorInfo.IME_ACTION_UNSPECIFIED && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)
return true;
return action == EditorInfo.IME_ACTION_DONE
|| action == EditorInfo.IME_ACTION_GO
|| action == EditorInfo.IME_ACTION_SEND;
}
public boolean matchesRequiredPINLength(String input) {
final PINInputView inputView = this.inputView.get();
if (inputView == null)
return false;
return inputView.matchesRequiredPINLength(input);
}
@Override
public void onTextEntered(int count) {
if (count == 4) {
var inputView = this.inputView.get();
InputEventListener eventListener = this.eventListener.get();
if (eventListener != null)
eventListener.onInputEntered(inputView.getText());
inputView.reset();
}
}
public interface InputEventListener {
void onInputEntered(String input);
}
}
@@ -0,0 +1,231 @@
package com.guardanis.applock.pin;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.WeakHashMap;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.ThemesUtils;
public class PINInputView extends LinearLayout implements TextWatcher {
private final WeakHashMap<PINItemView, PINItemAnimator> animators = new WeakHashMap<PINItemView, PINItemAnimator>();
private Paint itemTextPaint;
private Paint itemBackgroundPaint;
private int inputViewsCount = 10;
private PINItemView[] pinItemViews;
private EditText editText;
private String lastText = "";
private boolean passwordCharactersEnabled = true;
private String passwordCharacter;
private InputTextListener listener;
public PINInputView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
private void init(AttributeSet attrs) {
setWillNotDraw(false);
setupEditText();
itemTextPaint = new Paint();
itemTextPaint.setColor(ThemesUtils.getAccentColor());
itemBackgroundPaint = new Paint();
itemBackgroundPaint.setColor(ThemesUtils.getMutedAccentColor());
inputViewsCount = getResources().getInteger(AndroidUtils.getIdentifier("applock__input_pin_item_count", "integer"));
passwordCharacter = getResources().getString(AndroidUtils.getIdentifier("applock__password_char", "string"));
}
protected void setupEditText() {
removeAllViews();
this.editText = inflateFakeEditText();
this.editText.addTextChangedListener(this);
addView(editText);
}
protected EditText inflateFakeEditText() {
return (EditText) LayoutInflater.from(getContext())
.inflate(AndroidUtils.getIdentifier("applock__fake_edit_text", "layout"), this, false);
}
public PINInputView setInputViewsCount(int inputViewsCount) {
this.inputViewsCount = inputViewsCount;
return reset();
}
public PINInputView reset() {
this.lastText = "";
this.editText.setText("");
if (pinItemViews != null)
animateLastOut();
return this;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
ensureKeyboardVisible();
return true;
}
return super.onTouchEvent(event);
}
public void ensureKeyboardVisible() {
editText.requestFocus();
((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
.showSoftInput(editText, 0);
}
@Override
public void onDraw(Canvas canvas) {
if (pinItemViews == null || pinItemViews.length != inputViewsCount)
setupItemViews(canvas);
String text = editText.getText().toString();
for (int i = 0; i < pinItemViews.length; i++) {
String textItem = i < text.length()
? (passwordCharactersEnabled ? passwordCharacter : text.substring(i, i + 1))
: "";
pinItemViews[i].draw(canvas, textItem);
}
}
private void setupItemViews(Canvas canvas) {
pinItemViews = new PINItemView[inputViewsCount];
int cellWidth = canvas.getWidth() / inputViewsCount;
int largestRadius = Math.min(cellWidth / 2, canvas.getHeight() / 2);
int smallestRadius = (int) (largestRadius * Float.parseFloat(getResources().getString(AndroidUtils.getIdentifier("applock__empty_item_min_size_percent", "string"))));
int[] minMaxRadius = new int[]{smallestRadius, largestRadius};
itemTextPaint.setTextSize((int) (largestRadius * .85));
for (int i = 0; i < pinItemViews.length; i++) {
float[] positionInCanvas = getPositionInCanvas(canvas, i, cellWidth);
pinItemViews[i] = new PINItemView(positionInCanvas, minMaxRadius, itemTextPaint, itemBackgroundPaint);
}
}
private float[] getPositionInCanvas(Canvas canvas, int position, int cellWidth) {
return new float[]{
(cellWidth * position) + (cellWidth / 2),
canvas.getHeight() / 2
};
}
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
if (editable == null || lastText == null)
return;
String text = editable.toString();
if (inputViewsCount < text.length()) {
editText.setText(lastText);
editText.setSelection(editText.getText().length());
} else if (text.length() < lastText.length()) {
animateLastOut();
this.lastText = text;
} else if (lastText.length() < text.length()) {
animateLastIn();
this.lastText = text;
}
if (listener != null) {
listener.onTextEntered(text.length());
}
}
public void setInputTextListener(InputTextListener listener) {
this.listener = listener;
}
private void animateLastOut() {
int startingIndex = editText.getText().toString().length(); // One after current length, only happens on backspace
for (int i = pinItemViews.length - 1; startingIndex <= i; i--)
if (!pinItemViews[i].isAnimatedOut())
animate(pinItemViews[i], PINItemAnimator.ItemAnimationDirection.OUT);
}
private void animateLastIn() {
PINItemView item = pinItemViews[editText.getText().toString().length() - 1];
animate(item, PINItemAnimator.ItemAnimationDirection.IN);
}
private void animate(PINItemView view, PINItemAnimator.ItemAnimationDirection direction) {
cancelPreviousAnimation(view);
view.setAnimationDirection(direction);
PINItemAnimator animator = new PINItemAnimator(this, view, direction);
animators.put(view, animator);
animator.start();
}
private void cancelPreviousAnimation(PINItemView view) {
PINItemAnimator animator = animators.get(view);
try {
animator.cancel();
animators.put(view, null);
} catch (Exception e) {
e.printStackTrace();
}
}
public String getText() {
return editText.getText().toString();
}
public void setOnEditorActionListener(TextView.OnEditorActionListener actionListener) {
editText.setOnEditorActionListener(actionListener);
}
public void setPasswordCharactersEnabled(boolean passwordCharactersEnabled) {
this.passwordCharactersEnabled = passwordCharactersEnabled;
}
public void setPasswordCharacter(String passwordCharacter) {
this.passwordCharacter = passwordCharacter;
}
public boolean matchesRequiredPINLength(String input) {
return input.length() == inputViewsCount;
}
}
@@ -0,0 +1,92 @@
package com.guardanis.applock.pin;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import java.lang.ref.WeakReference;
import ru.vtosters.lite.utils.AndroidUtils;
public class PINItemAnimator extends Thread {
private static final int ANIMATION_DURATION = 150;
private static final int UPDATE_RATE = 25;
private final WeakReference<PINInputView> inputView;
private final PINItemView itemView;
private final ItemAnimationDirection animationDirection;
private final Interpolator itemInterpolator = new AccelerateDecelerateInterpolator();
private final float minSizePercent;
private long startTime;
private boolean canceled = false;
public PINItemAnimator(PINInputView inputView, PINItemView itemView, ItemAnimationDirection animationDirection) {
this.inputView = new WeakReference<>(inputView);
this.itemView = itemView;
this.animationDirection = animationDirection;
this.minSizePercent = Float.parseFloat(inputView.getResources().getString(AndroidUtils.getIdentifier("applock__empty_item_min_size_percent", "string")));
}
@Override
public void run() {
this.startTime = System.currentTimeMillis();
try {
if (animationDirection == ItemAnimationDirection.IN)
animateIn();
else
animateOut();
} catch (Exception e) {
e.printStackTrace();
}
}
private void animateIn() throws Exception {
float percent = minSizePercent;
while (percent < 1 && !canceled) {
percent = Math.min(minSizePercent + calculatePercentComplete(), 1);
updateView(percent);
Thread.sleep(UPDATE_RATE);
}
}
private void animateOut() throws Exception {
float percent = 1 - calculatePercentComplete();
while (minSizePercent < percent && !canceled) {
percent = Math.max(1 - calculatePercentComplete(), minSizePercent);
updateView(percent);
Thread.sleep(UPDATE_RATE);
}
}
protected float calculatePercentComplete() {
float completed = ((float) (System.currentTimeMillis() - startTime)) / ANIMATION_DURATION;
return itemInterpolator.getInterpolation(completed);
}
private void updateView(final float percent) {
final PINInputView inputView = this.inputView.get();
if (inputView == null)
return;
inputView.post(() -> {
itemView.onAnimationUpdate(percent);
inputView.invalidate();
});
}
public void cancel() {
this.canceled = true;
}
public enum ItemAnimationDirection {
IN, OUT
}
}
@@ -0,0 +1,61 @@
package com.guardanis.applock.pin;
import android.graphics.Canvas;
import android.graphics.Paint;
public class PINItemView {
private final float[] position;
private final int intendedRadius;
private int currentRadius;
private Paint backgroundPaint;
private float[] textPosition;
private Paint textPaint;
private PINItemAnimator.ItemAnimationDirection animationDirection = PINItemAnimator.ItemAnimationDirection.IN;
public PINItemView(float[] position, int[] minMaxRadius, Paint baseTextPaint, Paint baseBackgroundPaint) {
this.position = position;
this.intendedRadius = minMaxRadius[1];
this.currentRadius = minMaxRadius[0];
setupPaints(baseTextPaint, baseBackgroundPaint);
}
private void setupPaints(Paint baseTextPaint, Paint baseBackgroundPaint) {
this.textPaint = new Paint();
this.textPaint.setTextAlign(Paint.Align.CENTER);
this.textPaint.setAntiAlias(true);
this.textPaint.setColor(baseTextPaint.getColor());
this.textPaint.setTextSize(baseTextPaint.getTextSize());
this.textPosition = new float[]{
position[0],
position[1] - ((textPaint.descent() + textPaint.ascent()) / 2)
};
this.backgroundPaint = new Paint();
this.backgroundPaint.setTextAlign(Paint.Align.CENTER);
this.backgroundPaint.setAntiAlias(true);
this.backgroundPaint.setColor(baseBackgroundPaint.getColor());
}
public void draw(Canvas canvas, String textValue) {
canvas.drawCircle(position[0], position[1], currentRadius, backgroundPaint);
canvas.drawText(textValue, textPosition[0], textPosition[1], textPaint);
}
public void setAnimationDirection(PINItemAnimator.ItemAnimationDirection animationDirection) {
this.animationDirection = animationDirection;
}
public void onAnimationUpdate(float percentCompleted) {
this.currentRadius = (int) (intendedRadius * percentCompleted);
this.textPaint.setAlpha((int) (255 * percentCompleted));
}
public boolean isAnimatedOut() {
return animationDirection == PINItemAnimator.ItemAnimationDirection.OUT;
}
}
@@ -0,0 +1,169 @@
package com.guardanis.applock.services;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.core.content.ContextCompat;
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
import androidx.core.os.CancellationSignal;
import com.guardanis.applock.AppLock;
import com.guardanis.applock.utils.CipherGenerator;
import javax.crypto.Cipher;
import ru.vtosters.lite.utils.AndroidUtils;
public class FingerprintLockService extends LockService {
private static final String PREF_ENROLLMENT_ALLOWED = "pin__fingerprint_enrollment_allowed";
protected CancellationSignal fingerprintCancellationSignal;
@Override
public boolean isEnrollmentEligible(Context context) {
return Build.VERSION_CODES.M <= Build.VERSION.SDK_INT && context.getResources().getBoolean(AndroidUtils.getIdentifier("applock__fingerprint_service_enabled", "bool")) && isHardwarePresent(context);
}
public void enroll(Context context, AuthenticationDelegate delegate) {
authenticate(context, false, delegate);
}
public void authenticate(Context context, AuthenticationDelegate delegate) {
authenticate(context, true, delegate);
}
protected void authenticate(Context context, boolean localEnrollmentRequired, AuthenticationDelegate delegate) {
int errorCode = getRequiredResolutionErrorCode(context, localEnrollmentRequired);
if (-1 < errorCode) {
delegate.onResolutionRequired(errorCode);
return;
}
// Should be handled by getRequiredResolutionErrorCode(Context, boolean)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return;
attemptFingerprintManagerAuthentication(context, delegate);
}
/**
* @return the resolvable error code or -1 if there are no issues requiring a resolution
*/
protected int getRequiredResolutionErrorCode(Context context, boolean localEnrollmentRequired) {
FingerprintManagerCompat manager = FingerprintManagerCompat.from(context);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return AppLock.ERROR_CODE_SDK_VERSION_MINIMUM;
if (localEnrollmentRequired && !isEnrolled(context))
return AppLock.ERROR_CODE_FINGERPRINTS_NOT_LOCALLY_ENROLLED;
if (!isHardwarePresent(context)) return AppLock.ERROR_CODE_FINGERPRINTS_MISSING_HARDWARE;
if (!manager.hasEnrolledFingerprints()) return AppLock.ERROR_CODE_FINGERPRINTS_EMPTY;
if (ContextCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED)
return AppLock.ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED;
return -1;
}
protected void attemptFingerprintManagerAuthentication(final Context context, final AuthenticationDelegate delegate) {
this.fingerprintCancellationSignal = new CancellationSignal();
var callback = new FingerprintManagerCompat.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
super.onAuthenticationError(errMsgId, errString);
delegate.onResolutionRequired(AndroidUtils.getIdentifier("applock__fingerprint_error_unknown", "string"));
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
super.onAuthenticationHelp(helpMsgId, helpString);
delegate.onAuthenticationHelp(helpMsgId, helpString);
}
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
notifyEnrolled(context);
delegate.onAuthenticationSuccess(result);
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
}
};
delegate.onAuthenticating(fingerprintCancellationSignal);
try {
Cipher cipher = generateAuthCipher(context, false, 0);
FingerprintManagerCompat.CryptoObject cryptoObject = new FingerprintManagerCompat.CryptoObject(cipher);
FingerprintManagerCompat manager = FingerprintManagerCompat.from(context);
manager.authenticate(cryptoObject, 0, fingerprintCancellationSignal, callback, null);
} catch (Exception e) {
e.printStackTrace();
delegate.onResolutionRequired(AppLock.ERROR_CODE_FINGERPRINTS_MISSING_HARDWARE);
}
}
public boolean isHardwarePresent(Context context) {
return FingerprintManagerCompat.from(context).isHardwareDetected();
}
@Override
public boolean isEnrolled(Context context) {
return AppLock.getInstance(context).getPreferences().getBoolean(PREF_ENROLLMENT_ALLOWED, false);
}
protected void notifyEnrolled(Context context) {
AppLock.getInstance(context).getPreferences().edit().putBoolean(PREF_ENROLLMENT_ALLOWED, true).commit();
}
@SuppressLint("NewApi")
protected Cipher generateAuthCipher(Context context, boolean forceRegenerate, int attempts) throws Exception {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
throw new RuntimeException("generateAuthCipher() not supported before Api 23");
return new CipherGenerator().generateAuthCipher(context, forceRegenerate, attempts);
}
@Override
public void invalidateEnrollments(Context context) {
AppLock.getInstance(context).getPreferences().edit().putBoolean(PREF_ENROLLMENT_ALLOWED, false).commit();
}
@Override
public void cancelPendingAuthentications(Context context) {
if (fingerprintCancellationSignal != null) {
this.fingerprintCancellationSignal.cancel();
this.fingerprintCancellationSignal = null;
}
}
public interface AuthenticationDelegate {
void onResolutionRequired(int errorCode);
void onAuthenticationHelp(int code, CharSequence message);
void onAuthenticating(CancellationSignal cancellationSignal);
void onAuthenticationSuccess(FingerprintManagerCompat.AuthenticationResult result);
void onAuthenticationFailed(String message);
}
}
@@ -0,0 +1,14 @@
package com.guardanis.applock.services;
import android.content.Context;
public abstract class LockService {
public abstract boolean isEnrolled(Context context);
public abstract boolean isEnrollmentEligible(Context context);
public abstract void cancelPendingAuthentications(Context context);
public abstract void invalidateEnrollments(Context context);
}
@@ -0,0 +1,74 @@
package com.guardanis.applock.services;
import android.content.Context;
import com.guardanis.applock.AppLock;
import com.guardanis.applock.utils.CryptoUtils;
public class PINLockService extends LockService {
private static final String PREF_SAVED_LOCKED_PASSWORD = "pin__saved_locked_password";
@Override
public boolean isEnrollmentEligible(Context context) {
// I couldn't find a good reason to allow the disabling of the PIN as an option.
return true;
}
public void authenticate(Context context, String pin, AuthenticationDelegate eventListener) {
if (!isEnrolled(context)) {
eventListener.onNoPIN();
return;
}
if (!getEnrolledPIN(context).equals(CryptoUtils.encryptSha1(pin))) {
eventListener.onPINDoesNotMatch();
return;
}
eventListener.onPINMatches();
}
@Override
public boolean isEnrolled(Context context) {
return AppLock.getInstance(context)
.getPreferences()
.getString(PREF_SAVED_LOCKED_PASSWORD, null) != null;
}
private String getEnrolledPIN(Context context) {
return AppLock.getInstance(context)
.getPreferences()
.getString(PREF_SAVED_LOCKED_PASSWORD, null);
}
public void enroll(Context context, String pin) {
AppLock.getInstance(context)
.getPreferences()
.edit()
.putString(PREF_SAVED_LOCKED_PASSWORD, CryptoUtils.encryptSha1(pin))
.commit();
}
@Override
public void invalidateEnrollments(Context context) {
AppLock.getInstance(context)
.getPreferences()
.edit()
.remove(PREF_SAVED_LOCKED_PASSWORD)
.commit();
}
@Override
public void cancelPendingAuthentications(Context context) {
// There are none
}
public interface AuthenticationDelegate {
void onNoPIN();
void onPINDoesNotMatch();
void onPINMatches();
}
}
@@ -0,0 +1,69 @@
package com.guardanis.applock.utils;
import android.content.Context;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import androidx.annotation.RequiresApi;
import java.security.KeyStore;
import java.security.UnrecoverableKeyException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import ru.vtosters.lite.utils.AndroidUtils;
@RequiresApi(api = Build.VERSION_CODES.M)
public class CipherGenerator {
private static final String KEYSTORE_NAME = "AndroidKeyStore";
public Cipher generateAuthCipher(Context context, boolean forceRegenerate, int attempts) throws Exception {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
throw new RuntimeException("generateAuthCipher() not supported before Api 23");
String alias = context.getString(AndroidUtils.getIdentifier("applock__fingerprint_alias", "string"));
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_NAME);
keyStore.load(null);
if (forceRegenerate || !keyStore.containsAlias(alias)) {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_NAME);
keyGenerator.init(new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
keyGenerator.generateKey();
}
String cipherFormat = String.format(
"%s/%s/%s",
KeyProperties.KEY_ALGORITHM_AES,
KeyProperties.BLOCK_MODE_CBC,
KeyProperties.ENCRYPTION_PADDING_PKCS7);
try {
Cipher cipher = Cipher.getInstance(cipherFormat);
cipher.init(Cipher.ENCRYPT_MODE, keyStore.getKey(alias, null));
return cipher;
} catch (KeyPermanentlyInvalidatedException e) {
e.printStackTrace();
if (1 < attempts)
return generateAuthCipher(context, true, attempts + 1);
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
if (1 < attempts)
return generateAuthCipher(context, true, attempts + 1);
}
return null;
}
}
@@ -0,0 +1,40 @@
package com.guardanis.applock.utils;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public class CryptoUtils {
public static String encryptSha1(String text) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(text.getBytes(StandardCharsets.UTF_8), 0, text.length());
return convertToHex(md.digest());
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
private static String convertToHex(byte[] data) {
StringBuilder buf = new StringBuilder();
for (byte b : data) {
int half = (b >>> 4) & 0x0F;
int twoHalves = 0;
do {
buf.append((0 <= half) && (half <= 9)
? (char) ('0' + half)
: (char) ('a' + (half - 10)));
half = b & 0x0F;
}
while (twoHalves++ < 1);
}
return buf.toString();
}
}
@@ -0,0 +1,85 @@
package com.guardanis.applock.utils;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import java.lang.ref.WeakReference;
public class LifeCycleUtils {
public static AppLockActivityLifeCycleCallbacks attach(Activity activity, AppLockActivityLifeCycleCallbacks.Delegate delegate) {
AppLockActivityLifeCycleCallbacks callbacks = new AppLockActivityLifeCycleCallbacks(activity, delegate);
activity.getApplication()
.registerActivityLifecycleCallbacks(callbacks);
return callbacks;
}
public static class AppLockActivityLifeCycleCallbacks implements Application.ActivityLifecycleCallbacks {
protected WeakReference<Activity> openedActivity;
protected WeakReference<Delegate> delegate;
public AppLockActivityLifeCycleCallbacks(Activity activity, Delegate delegate) {
this.openedActivity = new WeakReference<>(activity);
this.delegate = new WeakReference<>(delegate);
}
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityResumed(Activity activity) {
Activity opened = this.openedActivity.get();
Delegate delegate = this.delegate.get();
if (opened == null || delegate == null || opened != activity)
return;
delegate.onActivityResumed();
}
@Override
public void onActivityPaused(Activity activity) {
Activity opened = this.openedActivity.get();
Delegate delegate = this.delegate.get();
if (opened == null || delegate == null || opened != activity)
return;
delegate.onActivityPaused();
}
@Override
public void onActivityDestroyed(Activity activity) {
Activity opened = this.openedActivity.get();
if (opened == null || opened != activity)
return;
opened.getApplication()
.unregisterActivityLifecycleCallbacks(this);
}
public interface Delegate {
void onActivityResumed();
void onActivityPaused();
}
}
}
@@ -0,0 +1,189 @@
package com.guardanis.applock.views;
import android.Manifest;
import android.app.Activity;
import android.app.Application;
import android.content.Intent;
import android.view.View;
import android.widget.TextView;
import androidx.core.app.ActivityCompat;
import com.guardanis.applock.AppLock;
import com.guardanis.applock.pin.PINInputController;
import com.guardanis.applock.pin.PINInputView;
import com.guardanis.applock.utils.LifeCycleUtils;
import java.lang.ref.WeakReference;
import ru.vtosters.lite.utils.AndroidUtils;
public abstract class AppLockViewController implements LifeCycleUtils.AppLockActivityLifeCycleCallbacks.Delegate {
protected PINInputController pinInputController;
protected WeakReference<Activity> activity;
protected WeakReference<View> parent;
protected WeakReference<PINInputView> pinInputView;
protected WeakReference<TextView> descriptionView;
protected WeakReference<View> actionSettings;
protected boolean autoAuthorizationEnabled = true;
protected Application.ActivityLifecycleCallbacks activityLifecycleCallbacks;
public AppLockViewController(Activity activity, View parent) {
this.activity = new WeakReference<>(activity);
this.parent = new WeakReference<>(parent);
this.descriptionView = new WeakReference((TextView) parent.findViewById(AndroidUtils.getIdentifier("pin__description", "id")));
this.actionSettings = new WeakReference<>(parent.findViewById(AndroidUtils.getIdentifier("pin__action_settings", "id")));
this.pinInputView = new WeakReference((PINInputView) parent.findViewById(AndroidUtils.getIdentifier("pin__input_view", "id")));
int inputViewsCount = parent.getResources()
.getInteger(AndroidUtils.getIdentifier("applock__input_pin_item_count", "integer"));
boolean passwordCharsEnabled = parent.getResources()
.getBoolean(AndroidUtils.getIdentifier("applock__item_password_chars_enabled", "bool"));
this.pinInputController = new PINInputController(pinInputView.get())
.setInputNumbersCount(inputViewsCount)
.setPasswordCharactersEnabled(passwordCharsEnabled);
this.activityLifecycleCallbacks = LifeCycleUtils.attach(activity, this);
}
public abstract void setupRootFlow();
public void setDescription(int descriptionResId) {
final TextView descriptionView = this.descriptionView.get();
if (descriptionView == null)
return;
descriptionView.setText(descriptionResId);
}
public void setDescription(String description) {
final TextView descriptionView = this.descriptionView.get();
if (descriptionView == null)
return;
descriptionView.setText(description);
}
protected <T extends View> void hide(WeakReference<T> weakView) {
final T view = weakView.get();
if (view == null)
return;
view.setVisibility(View.GONE);
}
protected <T extends View> void show(WeakReference<T> weakView) {
final T view = weakView.get();
if (view == null)
return;
view.setVisibility(View.VISIBLE);
}
public PINInputController getPINInputController() {
return pinInputController;
}
public View getParent() {
return parent.get();
}
public void setAutoAuthorizationEnabled(boolean autoAuthorizationEnabled) {
this.autoAuthorizationEnabled = autoAuthorizationEnabled;
}
public void updateActionSettings(final int errorCode) {
View actionSettings = this.actionSettings.get();
if (actionSettings == null)
return;
if (getSettingsIntent(errorCode) == null) {
actionSettings.setVisibility(View.GONE);
return;
}
actionSettings.setVisibility(View.VISIBLE);
actionSettings.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
handleActionSettingsClicked(errorCode);
}
});
}
protected abstract void handleActionSettingsClicked(int errorCode);
protected void handleInitialErrorPrompt(int errorCode) {
Activity activity = this.activity.get();
if (activity == null)
return;
switch (errorCode) {
case AppLock.ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED:
ActivityCompat.requestPermissions(
activity,
new String[]{Manifest.permission.USE_FINGERPRINT},
AppLock.REQUEST_CODE_FINGERPRINT_PERMISSION);
break;
default:
break;
}
}
protected int getDescriptionResIdForError(int errorCode) {
return switch (errorCode) {
case AppLock.ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED ->
AndroidUtils.getIdentifier("applock__fingerprint_error_permission", "string");
case AppLock.ERROR_CODE_FINGERPRINTS_EMPTY ->
AndroidUtils.getIdentifier("applock__fingerprint_error_none", "string");
case AppLock.ERROR_CODE_FINGERPRINTS_MISSING_HARDWARE ->
AndroidUtils.getIdentifier("applock__fingerprint_error_hardware", "string");
case AppLock.ERROR_CODE_FINGERPRINTS_NOT_LOCALLY_ENROLLED ->
AndroidUtils.getIdentifier("applock__fingerprint_error_not_enrolled", "string");
default -> AndroidUtils.getIdentifier("applock__fingerprint_error_unknown", "string");
};
}
/**
* @return an Intent directed towards the correct system setting for the error, or null if there is none.
*/
public Intent getSettingsIntent(int errorCode) {
return switch (errorCode) {
case AppLock.ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED, AppLock.ERROR_CODE_FINGERPRINTS_EMPTY, AppLock.ERROR_CODE_FINGERPRINTS_MISSING_HARDWARE ->
new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS);
default -> null;
};
}
public void unregisterReceivers() {
Activity activity = this.activity.get();
if (activity == null)
return;
activity.getApplication()
.unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks);
}
@Override
public void onActivityResumed() {
}
@Override
public void onActivityPaused() {
}
}
@@ -0,0 +1,253 @@
package com.guardanis.applock.views;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.view.View;
import androidx.core.content.ContextCompat;
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
import androidx.core.os.CancellationSignal;
import com.guardanis.applock.AppLock;
import com.guardanis.applock.pin.PINInputController;
import com.guardanis.applock.services.FingerprintLockService;
import com.guardanis.applock.services.PINLockService;
import java.lang.ref.WeakReference;
import ru.vtosters.lite.utils.AndroidUtils;
public class LockCreationViewController extends AppLockViewController
implements PINInputController.InputEventListener, FingerprintLockService.AuthenticationDelegate {
protected DisplayVariant displayVariant = DisplayVariant.NONE;
protected WeakReference<Delegate> delegate;
protected WeakReference<View> chooserParent;
protected String pinFirst;
public LockCreationViewController(Activity activity, View parent) {
super(activity, parent);
this.chooserParent = new WeakReference(parent.findViewById(AndroidUtils.getIdentifier("pin__create_chooser_items", "id")));
}
public LockCreationViewController setDelegate(Delegate delegate) {
this.delegate = new WeakReference<>(delegate);
return this;
}
@Override
public void setupRootFlow() {
Activity activity = this.activity.get();
if (activity == null)
return;
setupPINCreation();
}
protected void setupPINCreation() {
this.displayVariant = DisplayVariant.PIN_CREATION;
hide(chooserParent);
hide(actionSettings);
show(pinInputView);
setDescription(AndroidUtils.getIdentifier("applock__description_create_pin", "string"));
pinInputController.ensureKeyboardVisible();
pinInputController.setInputEventListener(this);
}
protected void setupPINConfirmation() {
this.displayVariant = DisplayVariant.PIN_CONFIRMATION;
hide(chooserParent);
hide(actionSettings);
show(pinInputView);
setDescription(AndroidUtils.getIdentifier("applock__description_confirm", "string"));
pinInputController.ensureKeyboardVisible();
pinInputController.setInputEventListener(this);
}
@Override
public void onInputEntered(String input) {
switch (displayVariant) {
case PIN_CREATION -> {
if (!pinInputController.matchesRequiredPINLength(input)) {
setDescription(AndroidUtils.getIdentifier("applock__unlock_error_insufficient_selection", "string"));
return;
}
this.pinFirst = input;
setupPINConfirmation();
}
case PIN_CONFIRMATION -> {
if (!pinInputController.matchesRequiredPINLength(input)) {
setDescription(AndroidUtils.getIdentifier("applock__unlock_error_insufficient_selection", "string"));
return;
}
if (!input.equals(pinFirst)) {
this.pinFirst = null;
setupPINCreation();
setDescription(AndroidUtils.getIdentifier("applock__description_create_pin_reattempt", "string"));
return;
}
createPINLock(input);
}
default -> {
}
}
}
protected void createPINLock(String input) {
Activity activity = this.activity.get();
if (activity == null)
return;
AppLock.getInstance(activity)
.getLockService(PINLockService.class)
.enroll(activity, input);
var fingerprintLockService = AppLock.getInstance(activity).getLockService(FingerprintLockService.class);
if (fingerprintLockService.isEnrollmentEligible(activity)) {
setupFingerprintAuthentication();
} else {
handleLockCreated();
}
}
protected void setupFingerprintAuthentication() {
this.displayVariant = DisplayVariant.FINGERPRINT_AUTHENTICATION;
hide(pinInputView);
hide(chooserParent);
hide(actionSettings);
setDescription(AndroidUtils.getIdentifier("applock__description_create_fingerprint", "string"));
if (autoAuthorizationEnabled)
attemptFingerprintAuthentication();
}
protected void attemptFingerprintAuthentication() {
Activity activity = this.activity.get();
if (activity == null)
return;
AppLock.getInstance(activity)
.getLockService(FingerprintLockService.class)
.enroll(activity, this);
}
@Override
public void onResolutionRequired(int errorCode) {
setDescription(getDescriptionResIdForError(errorCode));
updateActionSettings(errorCode);
handleInitialErrorPrompt(errorCode);
}
@Override
public void onAuthenticationHelp(int code, CharSequence message) {
Activity activity = this.activity.get();
if (activity == null)
return;
String unformattedHelpMessage = activity.getString(AndroidUtils.getIdentifier("applock__description_unlock_fingerprint_help", "string"));
String formatted = String.format(unformattedHelpMessage, message);
setDescription(formatted);
}
@Override
public void onAuthenticating(CancellationSignal cancellationSignal) {
}
@Override
public void onAuthenticationSuccess(FingerprintManagerCompat.AuthenticationResult result) {
handleLockCreated();
}
@Override
public void onAuthenticationFailed(String message) {
setDescription(message);
}
protected void handleLockCreated() {
this.displayVariant = DisplayVariant.NONE;
Delegate delegate = this.delegate.get();
if (delegate != null)
delegate.onLockCreated();
}
@Override
public void onActivityPaused() {
Activity activity = this.activity.get();
if (activity == null)
return;
AppLock.getInstance(activity)
.cancelPendingAuthentications();
if (displayVariant == DisplayVariant.FINGERPRINT_AUTHENTICATION)
setDescription(AndroidUtils.getIdentifier("applock__description_create_fingerprint_paused", "string"));
}
@Override
public void onActivityResumed() {
Activity activity = this.activity.get();
if (activity == null || displayVariant != DisplayVariant.FINGERPRINT_AUTHENTICATION)
return;
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
setDescription(AndroidUtils.getIdentifier("applock__fingerprint_error_permission_multiple", "string"));
updateActionSettings(AppLock.ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED);
return;
}
setDescription(AndroidUtils.getIdentifier("applock__description_create_fingerprint", "string"));
hide(actionSettings);
attemptFingerprintAuthentication();
}
@Override
protected void handleActionSettingsClicked(int errorCode) {
Activity activity = this.activity.get();
Intent intent = getSettingsIntent(errorCode);
if (activity == null || intent == null)
return;
activity.startActivity(intent);
}
public enum DisplayVariant {
NONE,
PIN_CREATION,
PIN_CONFIRMATION,
FINGERPRINT_AUTHENTICATION
}
public interface Delegate {
void onLockCreated();
}
}
@@ -0,0 +1,181 @@
package com.guardanis.applock.views;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.view.View;
import androidx.core.content.ContextCompat;
import com.guardanis.applock.AppLock;
import com.guardanis.applock.pin.PINInputController;
import com.guardanis.applock.services.FingerprintLockService;
import java.lang.ref.WeakReference;
import ru.vtosters.lite.utils.AndroidUtils;
public class UnlockViewController extends AppLockViewController implements AppLock.UnlockDelegate, PINInputController.InputEventListener {
protected DisplayVariant displayVariant = DisplayVariant.PIN_UNLOCK;
protected WeakReference<Delegate> delegate;
public UnlockViewController(Activity activity, View parent) {
super(activity, parent);
}
public UnlockViewController setDelegate(Delegate delegate) {
this.delegate = new WeakReference<Delegate>(delegate);
return this;
}
@Override
public void setupRootFlow() {
View parent = this.parent.get();
if (parent == null)
return;
setupPINUnlock();
}
protected void setupPINUnlock() {
this.displayVariant = DisplayVariant.PIN_UNLOCK;
hide(actionSettings);
show(pinInputView);
setDescription(AndroidUtils.getIdentifier("applock__description_unlock_pin", "string"));
pinInputController.ensureKeyboardVisible();
pinInputController.setInputEventListener(this);
FingerprintLockService fingerprintService = AppLock.getInstance(this.parent.get().getContext())
.getLockService(FingerprintLockService.class);
if (fingerprintService.isEnrolled(parent.get().getContext()))
attemptFingerprintAuthentication();
}
@Override
public void onInputEntered(String input) {
if (!pinInputController.matchesRequiredPINLength(input)) {
setDescription(AndroidUtils.getIdentifier("applock__unlock_error_insufficient_selection", "string"));
return;
}
attemptPINUnlock(input);
}
protected void attemptPINUnlock(String input) {
Activity activity = this.activity.get();
if (activity == null)
return;
AppLock.getInstance(activity)
.attemptPINUnlock(input, this);
}
protected void attemptFingerprintAuthentication() {
Activity activity = this.activity.get();
if (activity == null)
return;
AppLock.getInstance(activity)
.attemptFingerprintUnlock(this);
}
@Override
public void onUnlockSuccessful() {
this.displayVariant = DisplayVariant.NONE;
Delegate delegate = this.delegate.get();
if (delegate != null)
delegate.onUnlockSuccessful();
}
@Override
public void onResolutionRequired(int errorCode) {
setDescription(getDescriptionResIdForError(errorCode));
updateActionSettings(errorCode);
handleInitialErrorPrompt(errorCode);
}
@Override
public void onAuthenticationHelp(int code, String message) {
Activity activity = this.activity.get();
if (activity == null)
return;
String unformattedHelpMessage = activity.getString(AndroidUtils.getIdentifier("applock__description_unlock_fingerprint_help", "string"));
String formatted = String.format(unformattedHelpMessage, message);
setDescription(formatted);
}
@Override
public void onFailureLimitExceeded(String message) {
setDescription(message);
}
@Override
public void onActivityPaused() {
Activity activity = this.activity.get();
if (activity == null)
return;
AppLock.getInstance(activity)
.cancelPendingAuthentications();
if (displayVariant == DisplayVariant.FINGERPRINT_AUTHENTICATION)
setDescription(AndroidUtils.getIdentifier("applock__description_create_fingerprint_paused", "string"));
}
@Override
public void onActivityResumed() {
Activity activity = this.activity.get();
if (activity == null || displayVariant != DisplayVariant.FINGERPRINT_AUTHENTICATION)
return;
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
setDescription(AndroidUtils.getIdentifier("applock__fingerprint_error_permission_multiple", "string"));
updateActionSettings(AppLock.ERROR_CODE_FINGERPRINTS_PERMISSION_REQUIRED);
return;
}
setDescription(AndroidUtils.getIdentifier("applock__description_unlock_fingerprint", "string"));
hide(actionSettings);
attemptFingerprintAuthentication();
}
@Override
protected void handleActionSettingsClicked(int errorCode) {
Activity activity = this.activity.get();
Intent intent = getSettingsIntent(errorCode);
if (activity == null || intent == null)
return;
activity.startActivity(intent);
}
public enum DisplayVariant {
NONE,
PIN_UNLOCK,
FINGERPRINT_AUTHENTICATION
}
public interface Delegate {
void onUnlockSuccessful();
}
}
@@ -7,8 +7,12 @@ import static ru.vtosters.lite.utils.Preferences.checkupdates;
import static ru.vtosters.lite.utils.ThemesUtils.setNeededTheme;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import com.guardanis.applock.AppLock;
import com.guardanis.applock.activities.UnlockActivity;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
import ru.vtosters.lite.ssfs.UsersList;
@@ -38,6 +42,10 @@ public class MainActivityInjector {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannels.createChannels();
}
if (AppLock.isEnrolled(activity)) {
var intent = new Intent(activity, UnlockActivity.class);
activity.startActivity(intent);
}
// VKIDProtection.alert(activity);
}
@@ -1,16 +1,39 @@
package ru.vtosters.lite.ui.fragments;
import static android.widget.Toast.LENGTH_LONG;
import static android.widget.Toast.LENGTH_SHORT;
import static ru.vtosters.lite.ui.components.BackupManager.backupOnlines;
import static ru.vtosters.lite.ui.components.BackupManager.backupSettings;
import static ru.vtosters.lite.ui.components.BackupManager.deletePrefs;
import static ru.vtosters.lite.ui.components.BackupManager.restoreBackup;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
import static ru.vtosters.lite.utils.AndroidUtils.getDefaultPrefs;
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
import static ru.vtosters.lite.utils.LifecycleUtils.restartApplication;
import static ru.vtosters.lite.utils.LifecycleUtils.restartApplicationWithTimer;
import static ru.vtosters.lite.utils.ThemesUtils.getTextAttr;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.*;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.*;
import android.widget.ArrayAdapter;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.view.ContextThemeWrapper;
import b.h.g.m.FileUtils;
import com.vk.auth.api.VKAccount;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.core.util.ToastUtils;
@@ -21,29 +44,25 @@ import com.vk.im.ui.providers.audiomsg.PlayerActionSources;
import com.vk.imageloader.VKImageLoader;
import com.vk.media.player.cache.AutoPlayCacheHolder;
import com.vk.mediastore.MediaStorage;
import com.vk.navigation.Navigator;
import com.vk.pushes.PushSubscriber;
import com.vk.stickers.Stickers;
import com.vtosters.lite.R;
import com.vtosters.lite.auth.VKAccountManager;
import com.vtosters.lite.general.fragments.MaterialPreferenceToolbarFragment;
import com.vtosters.lite.im.ImEngineProvider;
import java.io.IOException;
import b.h.g.m.FileUtils;
import ru.vtosters.lite.deviceinfo.DeviceInfoCollector;
import ru.vtosters.lite.ssfs.UsersList;
import ru.vtosters.lite.ui.activities.VKAdminTokenActivity;
import ru.vtosters.lite.ui.components.BackupManager;
import ru.vtosters.lite.utils.*;
import java.io.IOException;
import static android.widget.Toast.LENGTH_LONG;
import static android.widget.Toast.LENGTH_SHORT;
import static ru.vtosters.lite.ui.components.BackupManager.*;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
import static ru.vtosters.lite.utils.AndroidUtils.*;
import static ru.vtosters.lite.utils.LifecycleUtils.restartApplication;
import static ru.vtosters.lite.utils.LifecycleUtils.restartApplicationWithTimer;
import static ru.vtosters.lite.utils.ThemesUtils.getTextAttr;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.NavigatorUtils;
import ru.vtosters.lite.utils.Preferences;
import ru.vtosters.lite.utils.VTVerifications;
public class OtherFragment extends MaterialPreferenceToolbarFragment {
private static final int VK_ADMIN_TOKEN_REQUEST_CODE = 1;
@@ -69,6 +88,11 @@ public class OtherFragment extends MaterialPreferenceToolbarFragment {
}
private void prefs() {
findPreference("pin").setOnPreferenceClickListener(preference -> {
NavigatorUtils.switchFragment(requireContext(), PinFragment.class);
return true;
});
findPreference("firebasefix").setOnPreferenceClickListener(preference -> {
VKAccount b = VKAccountManager.d();
PushSubscriber.e.a();
@@ -0,0 +1,30 @@
package ru.vtosters.lite.ui.fragments;
import android.content.Intent;
import android.os.Bundle;
import com.guardanis.applock.AppLock;
import com.guardanis.applock.activities.LockCreationActivity;
import com.vtosters.lite.general.fragments.MaterialPreferenceToolbarFragment;
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
public class PinFragment extends MaterialPreferenceToolbarFragment {
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
this.addPreferencesFromResource(com.vtosters.lite.R.xml.empty);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"Установить PIN-код",
"Установить PIN-код для входа в приложение",
null,
preference -> {
var intent = new Intent(this.getActivity(), LockCreationActivity.class);
startActivityForResult(intent, AppLock.REQUEST_CODE_LOCK_CREATION);
return true;
}
);
}
}
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/applock__create_choose_option_bg_pressed" android:state_selected="true"/>
<item android:drawable="@color/applock__create_choose_option_bg_pressed" android:state_pressed="true"/>
<item android:drawable="@color/applock__create_choose_option_bg" />
</selector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/applock__activity_lock_creation"
android:orientation="vertical"
android:padding="@dimen/applock__activity_padding">
<include
android:id="@+id/pin__container"
layout="@layout/applock__lock_creation" />
</LinearLayout>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/applock__activity_unlock"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/applock__activity_padding">
<include
android:id="@+id/pin__container"
layout="@layout/applock__unlock" />
</LinearLayout>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<EditText
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="1dp"
android:layout_height="1dp"
android:background="@android:color/transparent"
android:cursorVisible="false"
android:imeOptions="flagNoExtractUi|actionDone"
android:inputType="number"
android:textColor="@android:color/transparent"/>
@@ -0,0 +1,20 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:applock="http://schemas.android.com/apk/res-auto"
style="@style/applock__content_parent"
android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true">
<TextView
android:id="@+id/pin__description"
style="@style/applock__description" />
<com.guardanis.applock.pin.PINInputView
android:id="@+id/pin__input_view"
style="@style/applock__input_pin_view"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/pin__action_settings"
style="@style/applock__action_settings"/>
</LinearLayout>
+20
View File
@@ -0,0 +1,20 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:applock="http://schemas.android.com/apk/res-auto"
style="@style/applock__content_parent"
android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true">
<TextView
android:id="@+id/pin__description"
style="@style/applock__description"/>
<com.guardanis.applock.pin.PINInputView
android:id="@+id/pin__input_view"
style="@style/applock__input_pin_view"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/pin__action_settings"
style="@style/applock__action_settings"/>
</LinearLayout>
+4
View File
@@ -2511,4 +2511,8 @@
<attr name="im_ic_msgreload" format="reference" />
<attr name="im_ic_msgdl" format="reference" />
<attr name="im_ic_stats" format="reference" />
<declare-styleable name="PINInputView">
<attr name="pinTextColor" format="reference|color"/>
<attr name="pinBackgroundColor" format="reference|color"/>
</declare-styleable>
</resources>
+3
View File
@@ -17,4 +17,7 @@
<bool name="spb_default_reversed">false</bool>
<bool name="vk_is_tablet">false</bool>
<bool name="com_samsung_android_icon_container_has_icon_container">true</bool>
<bool name="applock__unlock_activity_return_allowed">false</bool>
<bool name="applock__item_password_chars_enabled">true</bool>
<bool name="applock__fingerprint_service_enabled">true</bool>
</resources>
+11
View File
@@ -686,4 +686,15 @@
<color name="yellow_light">#ffffd54f</color>
<color name="yellow_sunflower">#fffece00</color>
<color name="iclauncher">#fffcfcfc</color>
<color name="applock__activity_background">#FAFAFA</color>
<color name="applock__description_text">#7F8C8D</color>
<color name="applock__item_background">#3C9ADF</color>
<color name="applock__item_text">#FAFAFA</color>
<color name="applock__create_choose_option_bg">#00FFFFFF</color>
<color name="applock__create_choose_option_bg_pressed">#11000000</color>
<color name="applock__action_settings">#95A5A6</color>
</resources>
+16
View File
@@ -855,4 +855,20 @@
<item name="wrap_content" type="dimen">-2</item>
<dimen name="year_label_height">64.0dip</dimen>
<dimen name="year_label_text_size">22.0dip</dimen>
<dimen name="applock__activity_padding">24dip</dimen>
<dimen name="applock__content_padding_horizontal">24dip</dimen>
<dimen name="applock__content_padding_vertical">36dip</dimen>
<dimen name="applock__item_margin_top">20dip</dimen>
<dimen name="applock__item_size">50dip</dimen>
<dimen name="applock__fingerprint_scan_image_margin_top">35dip</dimen>
<dimen name="applock__fingerprint_scan_image_size">65dip</dimen>
<dimen name="applock__chooser_image_size">50dip</dimen>
<dimen name="applock__chooser_parent_padding_vertical">24dip</dimen>
<dimen name="applock__chooser_text_padding_top">12dip</dimen>
<dimen name="applock__description_text">18dip</dimen>
<dimen name="applock__description_padding_bottom">20dip</dimen>
<dimen name="applock__action_settings">14dip</dimen>
<dimen name="applock__action_settings_padding">12dip</dimen>
<dimen name="applock__action_settings_margin_top">24dip</dimen>
</resources>
+5
View File
@@ -3919,4 +3919,9 @@
<item name="download_mp3" type="id" />
<item name="remove_from_cache_vkx" type="id" />
<item name="add_to_cache_vkx" type="id" />
<item name="pin__description" type="id" />
<item name="pin__action_settings" type="id" />
<item name="pin__input_view" type="id" />
<item name="pin__container" type="id"/>
</resources>
+5
View File
@@ -43,4 +43,9 @@
<integer name="status_bar_notification_info_maxnum">999</integer>
<integer name="vkim_activity_animation_duration">200</integer>
<integer name="vkim_playing_drawable_rect_count">4</integer>
<integer name="applock__input_pin_item_count">4</integer>
<integer name="applock__max_retry_count">5</integer>
<integer name="applock__failure_retry_delay">5</integer>
<integer name="applock__activity_lock_reenable_minutes">15</integer>
</resources>
+28
View File
@@ -6499,4 +6499,32 @@ May look like a fake leaving the online"</string>
<string name="copydebuginfo_summ">To submit bug info to GitHub Issues</string>
<string name="device_info_copied">Device info copied</string>
<string name="app_open_by_default_settings">You need to allow the app to open links by default for the app to work properly</string>
<string name="applock__description_chooser">Secure this application with a PIN or your fingerprints?</string>
<string name="applock__description_create_pin">Create a PIN to secure this application.</string>
<string name="applock__description_create_pin_reattempt">PINs didn\'t match. Try again.</string>
<string name="applock__description_create_fingerprint">Scan your fingerprint to secure this application.</string>
<string name="applock__description_create_fingerprint_paused">Fingerprint authentication canceled. Reload this content to restart.</string>
<string name="applock__description_confirm">Re-enter your PIN to confirm.</string>
<string name="applock__description_unlock_pin">Enter your PIN to unlock.</string>
<string name="applock__description_unlock_fingerprint">Scan your fingerprint to unlock.</string>
<string name="applock__description_unlock_fingerprint_help">We\'re having trouble recognizing your fingerprint: %1$s</string>
<string name="applock__create_chooser_option_pin">PIN</string>
<string name="applock__create_chooser_option_fingerprint">Fingerprint</string>
<string name="applock__unlock_error_no_matching_pin_found">There is no saved PIN to unlock.</string>
<string name="applock__unlock_error_match_failed">The PINs don\'t match!</string>
<string name="applock__unlock_error_insufficient_selection">Wrong number of digits! Please try again</string>
<string name="applock__unlock_error_retry_limit_exceeded">You failed too many times. You may try again in %1$s.</string>
<string name="applock__toast_unlock_required">You can\'t do that</string>
<string name="applock__password_char">*</string>
<string name="applock__action_settings">Open System Settings</string>
<string name="applock__fingerprint_alias">AppLock</string>
<string name="applock__fingerprint_error_not_enrolled">You haven\'t setup fingerprint unlocking.</string>
<string name="applock__fingerprint_error_unrecognized">Your fingerprint wasn\'t recognized.</string>
<string name="applock__fingerprint_error_unknown">Something weird happened.</string>
<string name="applock__fingerprint_error_none">No fingerprints found! Enable them in your system settings.</string>
<string name="applock__fingerprint_error_hardware">Missing fingerprint hardware?</string>
<string name="applock__fingerprint_error_permission">Fingerprint permission required.</string>
<string name="applock__fingerprint_error_permission_multiple">Fingerprint permission denied. Please enable fingerprint support in your system settings.</string>
<item name="applock__empty_item_min_size_percent" format="float" type="string">0.2</item>
</resources>
+43
View File
@@ -10262,4 +10262,47 @@
<item name="android:shadowDy">-1.0</item>
<item name="android:shadowRadius">0.25</item>
</style>
<style name="applock__content_parent">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center_horizontal</item>
<item name="android:orientation">vertical</item>
<item name="android:paddingLeft">@dimen/applock__content_padding_horizontal</item>
<item name="android:paddingTop">@dimen/applock__content_padding_vertical</item>
<item name="android:paddingRight">@dimen/applock__content_padding_horizontal</item>
<item name="android:paddingBottom">@dimen/applock__content_padding_vertical</item>
</style>
<style name="applock__description">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center</item>
<item name="android:text">These is some kind of instructions you should follow.</item>
<item name="android:textColor">?text_primary</item>
<item name="android:textSize">@dimen/applock__description_text</item>
</style>
<style name="applock__input_pin_view">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/applock__item_size</item>
<item name="android:layout_marginTop">@dimen/applock__item_margin_top</item>
</style>
<style name="applock__input_fingerprint_view">
<item name="android:layout_width">@dimen/applock__fingerprint_scan_image_size</item>
<item name="android:layout_height">@dimen/applock__fingerprint_scan_image_size</item>
<item name="android:layout_marginTop">@dimen/applock__fingerprint_scan_image_margin_top</item>
</style>
<style name="applock__action_settings">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginTop">@dimen/applock__action_settings_margin_top</item>
<item name="android:gravity">center</item>
<item name="android:padding">@dimen/applock__action_settings_padding</item>
<item name="android:text">@string/applock__action_settings</item>
<item name="android:textColor">?text_primary</item>
<item name="android:textSize">@dimen/applock__action_settings</item>
</style>
</resources>
+5
View File
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="Настройки PIN">
<Preference
android:key="pin"
android:title="Установить PIN"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/other_fix_app">
<Preference
android:key="firebasefix"