/*
* MaxLock, an Xposed applock module for Android
* Copyright (C) 2014-2016 Max Rumpf alias Maxr1998
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.Maxr1998.xposed.maxlock.ui.lockscreen;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.SharedPreferences;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.CountDownTimer;
import android.support.annotation.DimenRes;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ContextThemeWrapper;
import android.support.v7.widget.AppCompatEditText;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.EditorInfo;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
import de.Maxr1998.xposed.maxlock.BuildConfig;
import de.Maxr1998.xposed.maxlock.Common;
import de.Maxr1998.xposed.maxlock.R;
import de.Maxr1998.xposed.maxlock.ui.actions.tasker.TaskerEventQueryReceiver;
import de.Maxr1998.xposed.maxlock.util.AuthenticationSucceededListener;
import de.Maxr1998.xposed.maxlock.util.MLPreferences;
import de.Maxr1998.xposed.maxlock.util.Util;
import static de.Maxr1998.xposed.maxlock.util.MLPreferences.getPreferencesKeys;
import static de.Maxr1998.xposed.maxlock.util.MLPreferences.getPreferencesKeysPerApp;
@SuppressLint("ViewConstructor")
public final class LockView extends RelativeLayout implements View.OnClickListener, View.OnLongClickListener {
private final int MAX_ATTEMPTS = 5;
private final String mPackageName, mActivityName;
private final String mLockingType, mPassword;
private final Point screenSize = new Point();
private final AuthenticationSucceededListener authenticationSucceededListener;
private final FrameLayout mContainer;
private final ViewGroup mInputBar;
private StringBuilder mCurrentKey = new StringBuilder(10);
private TextView mInputTextView;
private TextView mMessageArea;
private KnockCodeHelper mKnockCodeHolder;
public LockView(ContextThemeWrapper context, String packageName, String activityName) {
super(context);
try {
authenticationSucceededListener = (AuthenticationSucceededListener) getActivity();
} catch (ClassCastException e) {
throw new RuntimeException(getActivity().getClass().getSimpleName() + "must implement AuthenticationSucceededListener to use this fragment", e);
}
String title = packageName.equals(Common.MASTER_SWITCH_ON) ? getResources().getString(R.string.unlock_master_switch) :
Util.getApplicationNameFromPackage(packageName, getContext());
mPackageName = packageName.equals(Common.MASTER_SWITCH_ON) ? BuildConfig.APPLICATION_ID : packageName;
mActivityName = activityName;
mLockingType = getPreferencesKeysPerApp(getContext()).getString(mPackageName, getPrefs().getString(Common.LOCKING_TYPE, ""));
if (getPreferencesKeysPerApp(getContext()).contains(mPackageName)) {
mPassword = getPreferencesKeysPerApp(getContext()).getString(mPackageName + Common.APP_KEY_PREFERENCE, null);
} else {
mPassword = getPreferencesKeys(getContext()).getString(Common.KEY_PREFERENCE, "");
}
// Dimensions
getActivity().getWindowManager().getDefaultDisplay().getSize(screenSize);
LayoutInflater.from(getContext()).inflate(R.layout.lock_view, this, true);
TextView mTitleTextView = (TextView) findViewById(R.id.title_view);
mInputBar = (ViewGroup) findViewById(R.id.input_bar);
mInputTextView = (TextView) findViewById(R.id.input_view);
ImageButton mDeleteButton = (ImageButton) findViewById(R.id.delete_input);
mMessageArea = (TextView) findViewById(R.id.message_area);
mContainer = (FrameLayout) findViewById(R.id.container);
// Background
Util.getBackground(getActivity(), (ImageView) findViewById(R.id.background));
// Locking type view setup
switch (mLockingType) {
case Common.PREF_VALUE_PASSWORD:
case Common.PREF_VALUE_PASS_PIN:
mInputBar.removeAllViews();
mInputTextView = new AppCompatEditText(getContext());
LinearLayout.LayoutParams mInputTextParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT);
mInputTextParams.weight = 1;
mInputTextView.setLayoutParams(mInputTextParams);
mInputTextView.setSingleLine();
if (mLockingType.equals(Common.PREF_VALUE_PASS_PIN)) {
mInputTextView.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
} else {
mInputTextView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
}
mInputTextView.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE) {
if (checkInput()) {
Util.hideKeyboardFromWindow(getActivity(), LockView.this);
} else {
setKey(null, false);
v.startAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.shake));
handleFailedAttempt();
}
return true;
}
return false;
});
mInputTextView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
@Override
public void afterTextChanged(Editable editable) {
setKey(editable.toString(), false);
if (getPrefs().getBoolean(Common.ENABLE_QUICK_UNLOCK, false)) {
if (checkInput()) {
Util.hideKeyboardFromWindow(getActivity(), LockView.this);
}
}
}
});
mInputBar.addView(mInputTextView);
int dp16 = Util.dpToPx(getContext(), 16);
((LinearLayout.LayoutParams) mInputBar.getLayoutParams()).setMargins(dp16, 0, dp16, 0);
removeView(findViewById(R.id.fingerprint_stub));
FrameLayout fingerprintStub = new FrameLayout(getContext());
fingerprintStub.setId(R.id.fingerprint_stub);
mInputBar.addView(fingerprintStub);
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
break;
case Common.PREF_VALUE_PIN:
FrameLayout.LayoutParams pinParams = new FrameLayout.LayoutParams(getDimens(R.dimen.container_size), getDimens(R.dimen.container_size));
pinParams.gravity = isLandscape() ? Gravity.CENTER : Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
pinParams.bottomMargin = isLandscape() ? 0 : getDimens(R.dimen.fingerprint_margin);
mContainer.addView(new PinView(getContext(), this), pinParams);
break;
case Common.PREF_VALUE_KNOCK_CODE:
mKnockCodeHolder = new KnockCodeHelper(this, mContainer);
mContainer.setOnLongClickListener(this);
mContainer.setContentDescription(getResources().getString(R.string.content_description_lockscreen_container));
break;
case Common.PREF_VALUE_PATTERN:
mInputBar.setVisibility(View.GONE);
FrameLayout.LayoutParams patternParams = new FrameLayout.LayoutParams(getDimens(R.dimen.container_size), getDimens(R.dimen.container_size));
patternParams.gravity = Gravity.CENTER;
mContainer.addView(new PatternView(getContext(), this), patternParams);
break;
default:
handleAuthenticationSuccess();
return;
}
// Title
if (getPrefs().getBoolean(Common.HIDE_TITLE_BAR, false)) {
mTitleTextView.setVisibility(View.GONE);
} else {
mTitleTextView.setText(title);
Drawable icon = Util.getApplicationIconFromPackage(mPackageName, getContext());
if (icon != null)
icon.setBounds(0, 0, getDimens(R.dimen.title_icon_size), getDimens(R.dimen.title_icon_size));
mTitleTextView.setCompoundDrawables(icon, null, null, null);
mTitleTextView.setOnLongClickListener(this);
}
//Input
if (!mLockingType.equals(Common.PREF_VALUE_PASSWORD) && !mLockingType.equals(Common.PREF_VALUE_PASS_PIN)) {
if (getPrefs().getBoolean(Common.HIDE_INPUT_BAR, false)) {
mInputBar.setVisibility(View.GONE);
} else {
mInputTextView.setText("");
mDeleteButton.setOnClickListener(this);
mDeleteButton.setOnLongClickListener(this);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !getPrefs().getBoolean(Common.DISABLE_FINGERPRINT, false)) {
FingerprintView fv = new FingerprintView(getContext(), this);
((FrameLayout) findViewById(R.id.fingerprint_stub)).addView(fv);
}
// Handle timer for previous wrong attempts
if (isTimeLeft()) {
handleTimer();
}
}
/**
* Must be used as ContextThemeWrapper context for this LockView
*/
public static ContextThemeWrapper getThemedContext(Context baseContext) {
return new ContextThemeWrapper(baseContext, MLPreferences.getPreferences(baseContext).getBoolean(Common.INVERT_COLOR, false) ? R.style.AppTheme : R.style.AppTheme_Dark);
}
public SharedPreferences getPrefs() {
return MLPreferences.getPreferences(getContext());
}
public void appendToInput(String value) {
if (isTimeLeft()) {
return;
}
mInputTextView.append(value);
}
public void setKey(@Nullable String value, boolean append) {
if (isTimeLeft()) {
return;
}
mMessageArea.setText("");
if (value == null) {
mCurrentKey.setLength(0);
if (mKnockCodeHolder != null) {
mKnockCodeHolder.clear(true);
}
mInputTextView.setText("");
return;
}
if (!append) {
mCurrentKey.setLength(0);
}
mCurrentKey.append(value);
}
public void setPattern(List pattern, PatternView patternView) {
setKey(pattern.toString(), false);
if (!checkInput()) {
patternView.setWrong();
handleFailedAttempt();
}
}
public boolean checkInput() {
if (!isTimeLeft() && Util.shaHash(mCurrentKey.toString()).equals(mPassword) || mPassword.equals("")) {
handleAuthenticationSuccess();
return true;
}
return false;
}
public void handleAuthenticationSuccess() {
getPrefs().edit().putInt(Common.FAILED_ATTEMPTS_COUNTER, 0).apply();
authenticationSucceededListener.onAuthenticationSucceeded();
TaskerEventQueryReceiver.sendRequest(getActivity(), true, mPackageName);
}
public void handleFailedAttempt() {
if (isTimeLeft()) {
return;
}
mMessageArea.setText(R.string.message_wrong_password);
int old = getPrefs().getInt(Common.FAILED_ATTEMPTS_COUNTER, 0);
getPrefs().edit().putInt(Common.FAILED_ATTEMPTS_COUNTER, ++old).apply();
if (old % MAX_ATTEMPTS == 0) {
setKey(null, false);
getPrefs().edit().putLong(Common.FAILED_ATTEMPTS_TIMER, System.currentTimeMillis()).apply();
handleTimer();
}
TaskerEventQueryReceiver.sendRequest(getActivity(), false, mPackageName);
}
private void handleTimer() {
new CountDownTimer(getTimeLeft(), 200) {
@Override
public void onTick(long millisUntilFinished) {
mMessageArea.setText(getResources().getString(R.string.message_try_again_in_seconds, millisUntilFinished / 1000 + 1));
}
@Override
public void onFinish() {
mMessageArea.setText("");
}
}.start();
}
private long getTimeLeft() {
return 59000 + getPrefs().getLong(Common.FAILED_ATTEMPTS_TIMER, 0) - System.currentTimeMillis();
}
private boolean isTimeLeft() {
return getTimeLeft() > 0;
}
public boolean allowFingerprint() {
return !isTimeLeft() && getPrefs().getInt(Common.FAILED_ATTEMPTS_COUNTER, 0) < MAX_ATTEMPTS;
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.delete_input:
if (mCurrentKey.length() > 0) {
mCurrentKey.deleteCharAt(mCurrentKey.length() - 1);
if (mInputTextView.length() > 0) {
mInputTextView.setText(mInputTextView.getText().subSequence(0, mInputTextView.getText().length() - 1));
}
if (mKnockCodeHolder != null) {
mKnockCodeHolder.clear(false);
}
}
break;
}
}
@Override
public boolean onLongClick(View view) {
switch (view.getId()) {
case R.id.title_view:
Toast.makeText(getContext(), mActivityName, Toast.LENGTH_SHORT).show();
return true;
default:
setKey(null, false);
return true;
}
}
public String getPackageName() {
return mPackageName;
}
// Helpers
public AppCompatActivity getActivity() {
return (AppCompatActivity) ((ContextWrapper) getContext()).getBaseContext();
}
public int getDimens(@DimenRes int id) {
return getResources().getDimensionPixelSize(id);
}
public boolean isLandscape() {
return screenSize.x > screenSize.y;
}
}