mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-30 07:08:19 +00:00
Add the ability to opt out of PINs.
This commit is contained in:
@@ -100,6 +100,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
|
||||
import org.thoughtcrime.securesms.insights.InsightsLauncher;
|
||||
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
|
||||
|
||||
@@ -70,7 +70,7 @@ public class RefreshAttributesJob extends BaseJob {
|
||||
registrationLockV1 = TextSecurePreferences.getDeprecatedV1RegistrationLockPin(context);
|
||||
}
|
||||
|
||||
SignalServiceProfile.Capabilities capabilities = AppCapabilities.getCapabilities(kbsValues.hasPin());
|
||||
SignalServiceProfile.Capabilities capabilities = AppCapabilities.getCapabilities(kbsValues.hasPin() && !kbsValues.hasOptedOut());
|
||||
Log.i(TAG, "Calling setAccountAttributes() reglockV1? " + !TextUtils.isEmpty(registrationLockV1) + ", reglockV2? " + !TextUtils.isEmpty(registrationLockV2) + ", pin? " + kbsValues.hasPin() +
|
||||
"\n Capabilities:" +
|
||||
"\n Storage? " + capabilities.isStorage() +
|
||||
|
||||
@@ -20,6 +20,7 @@ public final class KbsValues extends SignalStoreValues {
|
||||
private static final String PIN = "kbs.pin";
|
||||
private static final String LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash";
|
||||
private static final String LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp";
|
||||
public static final String OPTED_OUT = "kbs.opted_out";
|
||||
|
||||
KbsValues(KeyValueStore store) {
|
||||
super(store);
|
||||
@@ -41,6 +42,7 @@ public final class KbsValues extends SignalStoreValues {
|
||||
.remove(LOCK_LOCAL_PIN_HASH)
|
||||
.remove(PIN)
|
||||
.remove(LAST_CREATE_FAILED_TIMESTAMP)
|
||||
.remove(OPTED_OUT)
|
||||
.commit();
|
||||
}
|
||||
|
||||
@@ -142,6 +144,33 @@ public final class KbsValues extends SignalStoreValues {
|
||||
return getLocalPinHash() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}.
|
||||
*/
|
||||
public synchronized void optIn() {
|
||||
putBoolean(OPTED_OUT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}.
|
||||
*/
|
||||
public synchronized void optOut() {
|
||||
putBoolean(OPTED_OUT, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}.
|
||||
*/
|
||||
public synchronized void resetMasterKey() {
|
||||
getStore().beginWrite()
|
||||
.remove(MASTER_KEY)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public synchronized boolean hasOptedOut() {
|
||||
return getBoolean(OPTED_OUT, false);
|
||||
}
|
||||
|
||||
public synchronized @Nullable TokenResponse getRegistrationLockTokenResponse() {
|
||||
String token = getStore().getString(TOKEN_RESPONSE, null);
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package org.thoughtcrime.securesms.lock.v2;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
@@ -13,11 +17,16 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import com.airbnb.lottie.LottieAnimationView;
|
||||
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.pin.PinOptOutDialog;
|
||||
import org.thoughtcrime.securesms.pin.PinState;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||
import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
|
||||
@@ -34,6 +43,12 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
|
||||
private LottieAnimationView lottieEnd;
|
||||
private ViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@@ -63,6 +78,10 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
|
||||
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.BaseKbsPinFragment__learn_more_url));
|
||||
});
|
||||
|
||||
Toolbar toolbar = view.findViewById(R.id.kbs_pin_toolbar);
|
||||
((AppCompatActivity) requireActivity()).setSupportActionBar(toolbar);
|
||||
((AppCompatActivity) requireActivity()).getSupportActionBar().setTitle(null);
|
||||
|
||||
initializeListeners();
|
||||
}
|
||||
|
||||
@@ -73,6 +92,34 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
|
||||
input.requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.pin_skip, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(@NonNull Menu menu) {
|
||||
if (RegistrationLockUtil.userHasRegistrationLock(requireContext()) ||
|
||||
SignalStore.kbsValues().hasPin())
|
||||
{
|
||||
menu.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_pin_learn_more:
|
||||
onLearnMore();
|
||||
return true;
|
||||
case R.id.menu_pin_skip:
|
||||
onPinSkipped();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract ViewModel initializeViewModel();
|
||||
|
||||
protected abstract void initializeViewStates();
|
||||
@@ -109,6 +156,15 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
|
||||
return confirm;
|
||||
}
|
||||
|
||||
protected void closeNavGraphBranch() {
|
||||
Intent activityIntent = requireActivity().getIntent();
|
||||
if (activityIntent != null && activityIntent.hasExtra("next_intent")) {
|
||||
startActivity(activityIntent.getParcelableExtra("next_intent"));
|
||||
}
|
||||
|
||||
requireActivity().finish();
|
||||
}
|
||||
|
||||
private void initializeViews(@NonNull View view) {
|
||||
title = view.findViewById(R.id.edit_kbs_pin_title);
|
||||
description = view.findViewById(R.id.edit_kbs_pin_description);
|
||||
@@ -152,4 +208,17 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
|
||||
return R.string.BaseKbsPinFragment__create_alphanumeric_pin;
|
||||
}
|
||||
}
|
||||
|
||||
private void onLearnMore() {
|
||||
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.KbsSplashFragment__learn_more_link));
|
||||
}
|
||||
|
||||
private void onPinSkipped() {
|
||||
PinOptOutDialog.showForSkip(requireContext(),
|
||||
this::closeNavGraphBranch,
|
||||
() -> {
|
||||
PinState.onPinCreateFailure();
|
||||
closeNavGraphBranch();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,15 +186,6 @@ public class ConfirmKbsPinFragment extends BaseKbsPinFragment<ConfirmKbsPinViewM
|
||||
.show();
|
||||
}
|
||||
|
||||
private void closeNavGraphBranch() {
|
||||
Intent activityIntent = requireActivity().getIntent();
|
||||
if (activityIntent != null && activityIntent.hasExtra("next_intent")) {
|
||||
startActivity(activityIntent.getParcelableExtra("next_intent"));
|
||||
}
|
||||
|
||||
requireActivity().finish();
|
||||
}
|
||||
|
||||
private void markMegaphoneSeenIfNecessary() {
|
||||
ApplicationDependencies.getMegaphoneRepository().markSeen(Megaphones.Event.PINS_FOR_ALL);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.lock.v2;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.PluralsRes;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package org.thoughtcrime.securesms.lock.v2;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
@@ -12,11 +13,16 @@ import android.widget.TextView;
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.pin.PinOptOutDialog;
|
||||
import org.thoughtcrime.securesms.pin.PinState;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
|
||||
public final class KbsSplashFragment extends Fragment {
|
||||
|
||||
@@ -25,6 +31,12 @@ public final class KbsSplashFragment extends Fragment {
|
||||
private TextView primaryAction;
|
||||
private TextView secondaryAction;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@@ -51,12 +63,42 @@ public final class KbsSplashFragment extends Fragment {
|
||||
|
||||
description.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
Toolbar toolbar = view.findViewById(R.id.kbs_splash_toolbar);
|
||||
((AppCompatActivity) requireActivity()).setSupportActionBar(toolbar);
|
||||
((AppCompatActivity) requireActivity()).getSupportActionBar().setTitle(null);
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() { }
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.pin_skip, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(@NonNull Menu menu) {
|
||||
if (RegistrationLockUtil.userHasRegistrationLock(requireContext())) {
|
||||
menu.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_pin_learn_more:
|
||||
onLearnMore();
|
||||
return true;
|
||||
case R.id.menu_pin_skip:
|
||||
onPinSkipped();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setUpRegLockEnabled() {
|
||||
title.setText(R.string.KbsSplashFragment__registration_lock_equals_pin);
|
||||
description.setText(R.string.KbsSplashFragment__your_registration_lock_is_now_called_a_pin);
|
||||
@@ -80,10 +122,15 @@ public final class KbsSplashFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void onLearnMore() {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.KbsSplashFragment__learn_more_link));
|
||||
}
|
||||
|
||||
intent.setData(Uri.parse(getString(R.string.KbsSplashFragment__learn_more_link)));
|
||||
|
||||
startActivity(intent);
|
||||
private void onPinSkipped() {
|
||||
PinOptOutDialog.showForSkip(requireContext(),
|
||||
() -> requireActivity().finish(),
|
||||
() -> {
|
||||
PinState.onPinCreateFailure();
|
||||
requireActivity().finish();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ class PinsForAllSchedule implements MegaphoneSchedule {
|
||||
}
|
||||
|
||||
private static boolean isEnabled() {
|
||||
if (SignalStore.kbsValues().hasOptedOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SignalStore.kbsValues().hasPin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ final class SignalPinReminderSchedule implements MegaphoneSchedule {
|
||||
|
||||
@Override
|
||||
public boolean shouldDisplay(int seenCount, long lastSeen, long firstVisible, long currentTime) {
|
||||
if (SignalStore.kbsValues().hasOptedOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SignalStore.kbsValues().hasPin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.thoughtcrime.securesms.pin;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class PinOptOutDialog {
|
||||
|
||||
private static final String TAG = Log.tag(PinOptOutDialog.class);
|
||||
|
||||
public static void showForSkip(@NonNull Context context, @NonNull Runnable onSuccess, @NonNull Runnable onFailed) {
|
||||
show(context,
|
||||
R.string.PinOptOutDialog_warning,
|
||||
R.string.PinOptOutDialog_skipping_pin_creation_will_create_a_hidden_high_entropy_pin,
|
||||
R.string.PinOptOutDialog_skip_pin_creation,
|
||||
true,
|
||||
onSuccess,
|
||||
onFailed);
|
||||
}
|
||||
|
||||
public static void showForOptOut(@NonNull Context context, @NonNull Runnable onSuccess, @NonNull Runnable onFailed) {
|
||||
show(context,
|
||||
R.string.PinOptOutDialog_warning,
|
||||
R.string.PinOptOutDialog_disabling_pins_will_create_a_hidden_high_entropy_pin,
|
||||
R.string.PinOptOutDialog_disable_pin,
|
||||
false,
|
||||
onSuccess,
|
||||
onFailed);
|
||||
}
|
||||
|
||||
private static void show(@NonNull Context context,
|
||||
@StringRes int titleRes,
|
||||
@StringRes int bodyRes,
|
||||
@StringRes int buttonRes,
|
||||
boolean skip,
|
||||
@NonNull Runnable onSuccess,
|
||||
@NonNull Runnable onFailed)
|
||||
{
|
||||
AlertDialog dialog = new AlertDialog.Builder(context)
|
||||
.setTitle(titleRes)
|
||||
.setMessage(bodyRes)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(buttonRes, (d, which) -> {
|
||||
d.dismiss();
|
||||
AlertDialog progress = SimpleProgressDialog.show(context);
|
||||
|
||||
SimpleTask.run(() -> {
|
||||
try {
|
||||
if (skip) {
|
||||
PinState.onPinCreationSkipped(context);
|
||||
} else {
|
||||
PinState.onPinOptOut(context);
|
||||
}
|
||||
return true;
|
||||
} catch (IOException | UnauthenticatedResponseException e) {
|
||||
Log.w(TAG, e);
|
||||
return false;
|
||||
}
|
||||
}, success -> {
|
||||
if (success) {
|
||||
onSuccess.run();
|
||||
} else {
|
||||
onFailed.run();
|
||||
}
|
||||
progress.dismiss();
|
||||
});
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, (d, which) -> d.dismiss())
|
||||
.create();
|
||||
|
||||
dialog.setOnShowListener(dialogInterface -> {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(ThemeUtil.getThemedColor(context, R.attr.dangerous_button_color));
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,6 @@ public class PinRestoreEntryFragment extends LoggingFragment {
|
||||
|
||||
errorLabel.setText(R.string.PinRestoreEntryFragment_incorrect_pin);
|
||||
helpButton.setVisibility(View.VISIBLE);
|
||||
skipButton.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
if (triesRemaining.getCount() == 1) {
|
||||
helpButton.setVisibility(View.VISIBLE);
|
||||
@@ -174,7 +173,6 @@ public class PinRestoreEntryFragment extends LoggingFragment {
|
||||
cancelSpinning(pinButton);
|
||||
pinEntry.setEnabled(true);
|
||||
enableAndFocusPinEntry();
|
||||
skipButton.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.annotation.WorkerThread;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.PinHashing;
|
||||
@@ -17,7 +18,9 @@ import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||
import org.thoughtcrime.securesms.registration.service.KeyBackupSystemWrongPinException;
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
@@ -27,7 +30,6 @@ import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.SignalStorage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
@@ -124,6 +126,8 @@ public final class PinState {
|
||||
* Invoked when the user is going through the PIN restoration flow (which is separate from reglock).
|
||||
*/
|
||||
public static synchronized void onSignalPinRestore(@NonNull Context context, @NonNull KbsPinData kbsData, @NonNull String pin) {
|
||||
Log.i(TAG, "onSignalPinRestore()");
|
||||
|
||||
SignalStore.kbsValues().setKbsMasterKey(kbsData, pin);
|
||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
|
||||
SignalStore.pinValues().resetPinReminders();
|
||||
@@ -152,19 +156,10 @@ public final class PinState {
|
||||
{
|
||||
Log.i(TAG, "onPinChangedOrCreated()");
|
||||
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
boolean isFirstPin = !kbsValues.hasPin();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
boolean isFirstPin = !SignalStore.kbsValues().hasPin() || SignalStore.kbsValues().hasOptedOut();
|
||||
|
||||
kbsValues.setKbsMasterKey(kbsData, pin);
|
||||
TextSecurePreferences.clearRegistrationLockV1(context);
|
||||
SignalStore.pinValues().setKeyboardType(keyboard);
|
||||
SignalStore.pinValues().resetPinReminders();
|
||||
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL);
|
||||
setPin(context, pin, keyboard);
|
||||
SignalStore.kbsValues().optIn();
|
||||
|
||||
if (isFirstPin) {
|
||||
Log.i(TAG, "First time setting a PIN. Refreshing attributes to set the 'storage' capability.");
|
||||
@@ -180,11 +175,42 @@ public final class PinState {
|
||||
* Invoked when PIN creation fails.
|
||||
*/
|
||||
public static synchronized void onPinCreateFailure() {
|
||||
Log.i(TAG, "onPinCreateFailure()");
|
||||
if (getState() == State.NO_REGISTRATION_LOCK) {
|
||||
SignalStore.kbsValues().onPinCreateFailure();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the user has enabled the "PIN opt out" setting.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onPinOptOut(@NonNull Context context)
|
||||
throws IOException, UnauthenticatedResponseException
|
||||
{
|
||||
Log.i(TAG, "onPinOptOutEnabled()");
|
||||
assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED, State.NO_REGISTRATION_LOCK);
|
||||
|
||||
optOutOfPin(context);
|
||||
|
||||
updateState(buildInferredStateFromOtherFields());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the user has chosen to skip PIN creation.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onPinCreationSkipped(@NonNull Context context)
|
||||
throws IOException, UnauthenticatedResponseException
|
||||
{
|
||||
Log.i(TAG, "onPinCreationSkipped()");
|
||||
assertState(State.NO_REGISTRATION_LOCK);
|
||||
|
||||
optOutOfPin(context);
|
||||
|
||||
updateState(buildInferredStateFromOtherFields());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked whenever a Signal PIN user enables registration lock.
|
||||
*/
|
||||
@@ -231,53 +257,6 @@ public final class PinState {
|
||||
updateState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when registration lock is disabled in the settings using the old UI (i.e. no mention of
|
||||
* Signal PINs).
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onDisableLegacyRegistrationLockPreference(@NonNull Context context)
|
||||
throws IOException, UnauthenticatedResponseException
|
||||
{
|
||||
Log.i(TAG, "onDisableRegistrationLockV1()");
|
||||
assertState(State.REGISTRATION_LOCK_V1);
|
||||
|
||||
Log.i(TAG, "Removing v1 registration lock pin from server");
|
||||
ApplicationDependencies.getSignalServiceAccountManager().removeRegistrationLockV1();
|
||||
TextSecurePreferences.clearRegistrationLockV1(context);
|
||||
|
||||
updateState(State.NO_REGISTRATION_LOCK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when registration lock is enabled in the settings using the old UI (i.e. no mention of
|
||||
* Signal PINs).
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onEnableLegacyRegistrationLockPreference(@NonNull Context context, @NonNull String pin)
|
||||
throws IOException, UnauthenticatedResponseException
|
||||
{
|
||||
Log.i(TAG, "onCompleteRegistrationLockV1Reminder()");
|
||||
assertState(State.NO_REGISTRATION_LOCK);
|
||||
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
|
||||
pinChangeSession.enableRegistrationLock(masterKey);
|
||||
|
||||
kbsValues.setKbsMasterKey(kbsData, pin);
|
||||
kbsValues.setV2RegistrationLockEnabled(true);
|
||||
TextSecurePreferences.clearRegistrationLockV1(context);
|
||||
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
|
||||
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL);
|
||||
|
||||
updateState(buildInferredStateFromOtherFields());
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be called by {@link org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob}.
|
||||
*/
|
||||
@@ -285,6 +264,8 @@ public final class PinState {
|
||||
public static synchronized void onMigrateToRegistrationLockV2(@NonNull Context context, @NonNull String pin)
|
||||
throws IOException, UnauthenticatedResponseException
|
||||
{
|
||||
Log.i(TAG, "onMigrateToRegistrationLockV2()");
|
||||
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
@@ -300,16 +281,12 @@ public final class PinState {
|
||||
updateState(buildInferredStateFromOtherFields());
|
||||
}
|
||||
|
||||
public static synchronized boolean shouldShowRegistrationLockV1Reminder() {
|
||||
return getState() == State.REGISTRATION_LOCK_V1;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static void bestEffortRefreshAttributes() {
|
||||
Optional<JobTracker.JobState> result = ApplicationDependencies.getJobManager().runSynchronously(new RefreshAttributesJob(), TimeUnit.SECONDS.toMillis(10));
|
||||
|
||||
if (result.isPresent() && result.get() == JobTracker.JobState.SUCCESS) {
|
||||
Log.w(TAG, "Attributes were refreshed successfully.");
|
||||
Log.i(TAG, "Attributes were refreshed successfully.");
|
||||
} else if (result.isPresent()) {
|
||||
Log.w(TAG, "Attribute refresh finished, but was not successful. Enqueuing one for later. (Result: " + result.get() + ")");
|
||||
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
|
||||
@@ -344,6 +321,37 @@ public final class PinState {
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static void setPin(@NonNull Context context, @NonNull String pin, @NonNull PinKeyboardType keyboard)
|
||||
throws IOException, UnauthenticatedResponseException
|
||||
{
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
|
||||
kbsValues.setKbsMasterKey(kbsData, pin);
|
||||
TextSecurePreferences.clearRegistrationLockV1(context);
|
||||
SignalStore.pinValues().setKeyboardType(keyboard);
|
||||
SignalStore.pinValues().resetPinReminders();
|
||||
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static void optOutOfPin(@NonNull Context context)
|
||||
throws IOException, UnauthenticatedResponseException
|
||||
{
|
||||
SignalStore.kbsValues().resetMasterKey();
|
||||
|
||||
setPin(context, Hex.toStringCondensed(Util.getSecretBytes(32)), PinKeyboardType.ALPHA_NUMERIC);
|
||||
SignalStore.kbsValues().optOut();
|
||||
|
||||
ApplicationDependencies.getJobManager().add(new StorageForcePushJob());
|
||||
bestEffortRefreshAttributes();
|
||||
}
|
||||
|
||||
private static @NonNull State assertState(State... allowed) {
|
||||
State currentState = getState();
|
||||
|
||||
@@ -358,6 +366,7 @@ public final class PinState {
|
||||
case REGISTRATION_LOCK_V1: throw new InvalidState_RegistrationLockV1();
|
||||
case PIN_WITH_REGISTRATION_LOCK_ENABLED: throw new InvalidState_PinWithRegistrationLockEnabled();
|
||||
case PIN_WITH_REGISTRATION_LOCK_DISABLED: throw new InvalidState_PinWithRegistrationLockDisabled();
|
||||
case PIN_OPT_OUT: throw new InvalidState_PinOptOut();
|
||||
default: throw new IllegalStateException("Expected: " + Arrays.toString(allowed) + ", Actual: " + currentState);
|
||||
}
|
||||
}
|
||||
@@ -386,6 +395,11 @@ public final class PinState {
|
||||
boolean v1Enabled = TextSecurePreferences.isV1RegistrationLockEnabled(context);
|
||||
boolean v2Enabled = kbsValues.isV2RegistrationLockEnabled();
|
||||
boolean hasPin = kbsValues.hasPin();
|
||||
boolean optedOut = kbsValues.hasOptedOut();
|
||||
|
||||
if (optedOut && !v2Enabled && !v1Enabled) {
|
||||
return State.PIN_OPT_OUT;
|
||||
}
|
||||
|
||||
if (!v1Enabled && !v2Enabled && !hasPin) {
|
||||
return State.NO_REGISTRATION_LOCK;
|
||||
@@ -427,7 +441,13 @@ public final class PinState {
|
||||
/**
|
||||
* User has a PIN, but registration lock is disabled.
|
||||
*/
|
||||
PIN_WITH_REGISTRATION_LOCK_DISABLED("pin_with_registration_lock_disabled");
|
||||
PIN_WITH_REGISTRATION_LOCK_DISABLED("pin_with_registration_lock_disabled"),
|
||||
|
||||
/**
|
||||
* The user has opted out of creating a PIN. In this case, we will generate a high-entropy PIN
|
||||
* on their behalf.
|
||||
*/
|
||||
PIN_OPT_OUT("pin_opt_out");
|
||||
|
||||
/**
|
||||
* Using a string key so that people can rename/reorder values in the future without breaking
|
||||
@@ -463,4 +483,5 @@ public final class PinState {
|
||||
private static class InvalidState_RegistrationLockV1 extends IllegalStateException {}
|
||||
private static class InvalidState_PinWithRegistrationLockEnabled extends IllegalStateException {}
|
||||
private static class InvalidState_PinWithRegistrationLockDisabled extends IllegalStateException {}
|
||||
private static class InvalidState_PinOptOut extends IllegalStateException {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.pin.PinOptOutDialog;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment {
|
||||
|
||||
private static final String PREF_ENABLE = "pref_pin_enable";
|
||||
private static final String PREF_DISABLE = "pref_pin_disable";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.preferences_advanced_pin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
updatePreferenceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
if (requestCode == CreateKbsPinActivity.REQUEST_NEW_PIN && resultCode == CreateKbsPinActivity.RESULT_OK) {
|
||||
Snackbar.make(requireView(), R.string.ApplicationPreferencesActivity_pin_created, Snackbar.LENGTH_LONG).setTextColor(Color.WHITE).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePreferenceState() {
|
||||
Preference enable = this.findPreference(PREF_ENABLE);
|
||||
Preference disable = this.findPreference(PREF_DISABLE);
|
||||
|
||||
if (SignalStore.kbsValues().hasOptedOut()) {
|
||||
enable.setVisible(true);
|
||||
disable.setVisible(false);
|
||||
|
||||
enable.setOnPreferenceClickListener(preference -> {
|
||||
onPreferenceChanged(true);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
enable.setVisible(false);
|
||||
disable.setVisible(true);
|
||||
|
||||
disable.setOnPreferenceClickListener(preference -> {
|
||||
onPreferenceChanged(false);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__advanced_pin_settings);
|
||||
}
|
||||
|
||||
private void onPreferenceChanged(boolean enabled) {
|
||||
boolean hasRegistrationLock = TextSecurePreferences.isV1RegistrationLockEnabled(requireContext()) ||
|
||||
SignalStore.kbsValues().isV2RegistrationLockEnabled();
|
||||
|
||||
if (!enabled && hasRegistrationLock) {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setMessage(R.string.ApplicationPreferencesActivity_pins_are_required_for_registration_lock)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(android.R.string.ok, (d, which) -> d.dismiss())
|
||||
.show();
|
||||
} else if (!enabled) {
|
||||
PinOptOutDialog.showForOptOut(requireContext(),
|
||||
() -> {
|
||||
updatePreferenceState();
|
||||
Snackbar.make(requireView(), R.string.ApplicationPreferencesActivity_pin_disabled, Snackbar.LENGTH_SHORT).setTextColor(Color.WHITE).show();
|
||||
},
|
||||
() -> Toast.makeText(requireContext(), R.string.ApplicationPreferencesActivity_failed_to_disable_pins_try_again_later, Toast.LENGTH_LONG).show());
|
||||
} else {
|
||||
startActivityForResult(CreateKbsPinActivity.getIntentForPinCreate(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,16 +24,23 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity;
|
||||
import org.thoughtcrime.securesms.pin.PinOptOutDialog;
|
||||
import org.thoughtcrime.securesms.pin.PinState;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -43,6 +50,7 @@ public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment {
|
||||
private static final String PUSH_MESSAGING_PREF = "pref_toggle_push_messaging";
|
||||
private static final String SUBMIT_DEBUG_LOG_PREF = "pref_submit_debug_logs";
|
||||
private static final String INTERNAL_PREF = "pref_internal";
|
||||
private static final String ADVANCED_PIN_PREF = "pref_advanced_pin_settings";
|
||||
|
||||
private static final int PICK_IDENTITY_CONTACT = 1;
|
||||
|
||||
@@ -56,6 +64,17 @@ public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment {
|
||||
submitDebugLog.setOnPreferenceClickListener(new SubmitDebugLogListener());
|
||||
submitDebugLog.setSummary(getVersion(getActivity()));
|
||||
|
||||
Preference pinSettings = this.findPreference(ADVANCED_PIN_PREF);
|
||||
pinSettings.setOnPreferenceClickListener(preference -> {
|
||||
requireActivity().getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end)
|
||||
.replace(android.R.id.content, new AdvancedPinPreferenceFragment())
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
return false;
|
||||
});
|
||||
|
||||
Preference internalPreference = this.findPreference(INTERNAL_PREF);
|
||||
internalPreference.setVisible(FeatureFlags.internalUser());
|
||||
internalPreference.setOnPreferenceClickListener(preference -> {
|
||||
|
||||
@@ -119,9 +119,10 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
SwitchPreferenceCompat signalPinReminders = (SwitchPreferenceCompat) this.findPreference(PinValues.PIN_REMINDERS_ENABLED);
|
||||
SwitchPreferenceCompat registrationLockV2 = (SwitchPreferenceCompat) this.findPreference(KbsValues.V2_LOCK_ENABLED);
|
||||
|
||||
if (SignalStore.kbsValues().hasPin()) {
|
||||
if (SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut()) {
|
||||
signalPinCreateChange.setOnPreferenceClickListener(new KbsPinUpdateListener());
|
||||
signalPinCreateChange.setTitle(R.string.preferences_app_protection__change_your_pin);
|
||||
signalPinReminders.setEnabled(true);
|
||||
registrationLockV2.setEnabled(true);
|
||||
} else {
|
||||
signalPinCreateChange.setOnPreferenceClickListener(new KbsPinCreateListener());
|
||||
|
||||
Reference in New Issue
Block a user