diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PinValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PinValues.java index 828d646c5f..2b5821e787 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PinValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PinValues.java @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.keyvalue; -import androidx.annotation.CheckResult; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -21,6 +20,7 @@ public final class PinValues { private static final String NEXT_INTERVAL = "pin.interval_index"; private static final String KEYBOARD_TYPE = "kbs.keyboard_type"; private static final String PIN_STATE = "pin.pin_state"; + public static final String PIN_REMINDERS_ENABLED = "pin.pin_reminders_enabled"; private final KeyValueStore store; @@ -81,6 +81,14 @@ public final class PinValues { .commit(); } + public void setPinRemindersEnabled(boolean enabled) { + store.beginWrite().putBoolean(PIN_REMINDERS_ENABLED, enabled).apply(); + } + + public boolean arePinRemindersEnabled() { + return store.getBoolean(PIN_REMINDERS_ENABLED, true); + } + public @NonNull PinKeyboardType getKeyboardType() { return PinKeyboardType.fromCode(store.getString(KEYBOARD_TYPE, null)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/megaphone/SignalPinReminderSchedule.java b/app/src/main/java/org/thoughtcrime/securesms/megaphone/SignalPinReminderSchedule.java index 29bbafa8b0..6da7d66819 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/megaphone/SignalPinReminderSchedule.java +++ b/app/src/main/java/org/thoughtcrime/securesms/megaphone/SignalPinReminderSchedule.java @@ -15,6 +15,10 @@ final class SignalPinReminderSchedule implements MegaphoneSchedule { return false; } + if (!SignalStore.pinValues().arePinRemindersEnabled()) { + return false; + } + long lastSuccessTime = SignalStore.pinValues().getLastSuccessfulEntryTime(); long interval = SignalStore.pinValues().getCurrentInterval(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java index f0711c8a45..9ced2f337c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java @@ -3,11 +3,26 @@ package org.thoughtcrime.securesms.preferences; import android.app.KeyguardManager; import android.content.Context; import android.content.Intent; +import android.graphics.Typeface; import android.os.Bundle; +import android.text.InputType; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.autofill.HintConstants; +import androidx.core.app.DialogCompat; +import androidx.core.view.ViewCompat; import androidx.preference.CheckBoxPreference; import androidx.preference.Preference; @@ -19,15 +34,20 @@ import org.thoughtcrime.securesms.BlockedContactsActivity; import org.thoughtcrime.securesms.PassphraseChangeActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.SwitchPreferenceCompat; +import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; import org.thoughtcrime.securesms.keyvalue.KbsValues; +import org.thoughtcrime.securesms.keyvalue.PinValues; import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.lock.PinHashing; import org.thoughtcrime.securesms.lock.RegistrationLockV1Dialog; +import org.thoughtcrime.securesms.lock.SignalPinReminderDialog; import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity; +import org.thoughtcrime.securesms.lock.v2.KbsConstants; import org.thoughtcrime.securesms.lock.v2.RegistrationLockUtil; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.pin.PinState; @@ -37,13 +57,17 @@ import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.storage.StorageSyncHelper; import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.FeatureFlags; +import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; +import org.w3c.dom.Text; import java.io.IOException; import java.util.Locale; +import java.util.Objects; import java.util.concurrent.TimeUnit; import mobi.upod.timedurationpicker.TimeDurationPickerDialog; @@ -62,10 +86,15 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment super.onCreate(paramBundle); disablePassphrase = (CheckBoxPreference) this.findPreference("pref_enable_passphrase_temporary"); + this.findPreference(KbsValues.V2_LOCK_ENABLED).setPreferenceDataStore(SignalStore.getPreferenceDataStore()); ((SwitchPreferenceCompat) this.findPreference(KbsValues.V2_LOCK_ENABLED)).setChecked(SignalStore.kbsValues().isV2RegistrationLockEnabled()); this.findPreference(KbsValues.V2_LOCK_ENABLED).setOnPreferenceChangeListener(new RegistrationLockV2ChangedListener()); + this.findPreference(PinValues.PIN_REMINDERS_ENABLED).setPreferenceDataStore(SignalStore.getPreferenceDataStore()); + ((SwitchPreferenceCompat) this.findPreference(PinValues.PIN_REMINDERS_ENABLED)).setChecked(SignalStore.pinValues().arePinRemindersEnabled()); + this.findPreference(PinValues.PIN_REMINDERS_ENABLED).setOnPreferenceChangeListener(new PinRemindersChangedListener()); + this.findPreference(TextSecurePreferences.SCREEN_LOCK).setOnPreferenceChangeListener(new ScreenLockListener()); this.findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setOnPreferenceClickListener(new ScreenLockTimeoutListener()); @@ -102,6 +131,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment SwitchPreferenceCompat registrationLockV1 = (SwitchPreferenceCompat) this.findPreference(TextSecurePreferences.REGISTRATION_LOCK_PREF_V1); Preference signalPinGroup = this.findPreference("prefs_signal_pin"); Preference signalPinCreateChange = this.findPreference(TextSecurePreferences.SIGNAL_PIN_CHANGE); + SwitchPreferenceCompat signalPinReminders = (SwitchPreferenceCompat) this.findPreference(PinValues.PIN_REMINDERS_ENABLED); SwitchPreferenceCompat registrationLockV2 = (SwitchPreferenceCompat) this.findPreference(KbsValues.V2_LOCK_ENABLED); @@ -115,6 +145,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment } else { signalPinCreateChange.setOnPreferenceClickListener(new KbsPinCreateListener()); signalPinCreateChange.setTitle(R.string.preferences_app_protection__create_a_pin); + signalPinReminders.setEnabled(false); registrationLockV2.setEnabled(false); } } else { @@ -430,4 +461,75 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment return false; } } + + private class PinRemindersChangedListener implements Preference.OnPreferenceChangeListener { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + boolean value = (boolean) newValue; + + if (!value) { + Context context = preference.getContext(); + DisplayMetrics metrics = preference.getContext().getResources().getDisplayMetrics(); + AlertDialog dialog = new AlertDialog.Builder(context, ThemeUtil.isDarkTheme(context) ? R.style.Theme_Signal_AlertDialog_Dark_Cornered_ColoredAccent : R.style.Theme_Signal_AlertDialog_Light_Cornered_ColoredAccent) + .setView(R.layout.pin_disable_reminders_dialog) + .create(); + + + dialog.show(); + dialog.getWindow().setLayout((int)(metrics.widthPixels * .80), ViewGroup.LayoutParams.WRAP_CONTENT); + + EditText pinEditText = (EditText) DialogCompat.requireViewById(dialog, R.id.reminder_disable_pin); + TextView statusText = (TextView) DialogCompat.requireViewById(dialog, R.id.reminder_disable_status); + View cancelButton = DialogCompat.requireViewById(dialog, R.id.reminder_disable_cancel); + View turnOffButton = DialogCompat.requireViewById(dialog, R.id.reminder_disable_turn_off); + + pinEditText.post(() -> { + if (pinEditText.requestFocus()) { + ServiceUtil.getInputMethodManager(pinEditText.getContext()).showSoftInput(pinEditText, 0); + } + }); + + ViewCompat.setAutofillHints(pinEditText, HintConstants.AUTOFILL_HINT_PASSWORD); + + switch (SignalStore.pinValues().getKeyboardType()) { + case NUMERIC: + pinEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD); + break; + case ALPHA_NUMERIC: + pinEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + break; + default: + throw new AssertionError("Unexpected type!"); + } + + pinEditText.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void onTextChanged(String text) { + turnOffButton.setEnabled(text.length() >= KbsConstants.MINIMUM_PIN_LENGTH); + } + }); + + pinEditText.setTypeface(Typeface.DEFAULT); + + turnOffButton.setOnClickListener(v -> { + String pin = pinEditText.getText().toString(); + boolean correct = PinHashing.verifyLocalPinHash(Objects.requireNonNull(SignalStore.kbsValues().getLocalPinHash()), pin); + + if (correct) { + SignalStore.pinValues().setPinRemindersEnabled(false); + ((SwitchPreferenceCompat) findPreference(PinValues.PIN_REMINDERS_ENABLED)).setChecked(false); + dialog.dismiss(); + } else { + statusText.setText(R.string.preferences_app_protection__incorrect_pin_try_again); + } + }); + + cancelButton.setOnClickListener(v -> dialog.dismiss()); + + return false; + } else { + return true; + } + } + } } diff --git a/app/src/main/res/layout/pin_disable_reminders_dialog.xml b/app/src/main/res/layout/pin_disable_reminders_dialog.xml new file mode 100644 index 0000000000..ecb3f98a9e --- /dev/null +++ b/app/src/main/res/layout/pin_disable_reminders_dialog.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + +