Update screen lock.

This commit is contained in:
Michelle Tang
2024-08-08 14:24:08 -04:00
committed by mtang-signal
parent c880db0f4a
commit 3bdbd69a7d
40 changed files with 744 additions and 259 deletions

View File

@@ -33,7 +33,7 @@ public final class AppInitialization {
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
TextSecurePreferences.setLastVersionCode(context, Util.getCanonicalVersionCode());
TextSecurePreferences.setHasSeenStickerIntroTooltip(context, true);
TextSecurePreferences.setPasswordDisabled(context, true);
SignalStore.settings().setPassphraseDisabled(true);
TextSecurePreferences.setReadReceiptsEnabled(context, true);
TextSecurePreferences.setTypingIndicatorsEnabled(context, true);
TextSecurePreferences.setHasSeenWelcomeScreen(context, false);
@@ -54,7 +54,7 @@ public final class AppInitialization {
SignalStore.onFirstEverAppLaunch();
SignalStore.onboarding().clearAll();
TextSecurePreferences.onPostBackupRestore(context);
TextSecurePreferences.setPasswordDisabled(context, true);
SignalStore.settings().setPassphraseDisabled(true);
AppDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
AppDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
@@ -73,7 +73,7 @@ public final class AppInitialization {
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
TextSecurePreferences.setLastVersionCode(context, Util.getCanonicalVersionCode());
TextSecurePreferences.setHasSeenStickerIntroTooltip(context, true);
TextSecurePreferences.setPasswordDisabled(context, true);
SignalStore.settings().setPassphraseDisabled(true);
AppDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
SignalStore.onFirstEverAppLaunch();
AppDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));

View File

@@ -376,12 +376,12 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
Log.i(TAG, "Setting first install version to " + BuildConfig.CANONICAL_VERSION_CODE);
TextSecurePreferences.setFirstInstallVersion(this, BuildConfig.CANONICAL_VERSION_CODE);
} else if (!TextSecurePreferences.isPasswordDisabled(this) && VersionTracker.getDaysSinceFirstInstalled(this) < 90) {
} else if (!SignalStore.settings().getPassphraseDisabled() && VersionTracker.getDaysSinceFirstInstalled(this) < 90) {
Log.i(TAG, "Detected a new install that doesn't have passphrases disabled -- assuming bad initialization.");
AppInitialization.onRepairFirstEverAppLaunch(this);
} else if (!TextSecurePreferences.isPasswordDisabled(this) && VersionTracker.getDaysSinceFirstInstalled(this) < 912) {
} else if (!SignalStore.settings().getPassphraseDisabled() && VersionTracker.getDaysSinceFirstInstalled(this) < 912) {
Log.i(TAG, "Detected a not-recent install that doesn't have passphrases disabled -- disabling now.");
TextSecurePreferences.setPasswordDisabled(this, true);
SignalStore.settings().setPassphraseDisabled(true);
}
}

View File

@@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.util.ServiceUtil
class BiometricDeviceAuthentication(
private val biometricManager: BiometricManager,
private val biometricPrompt: BiometricPrompt,
private val biometricPromptInfo: PromptInfo
private var biometricPromptInfo: PromptInfo
) {
companion object {
const val AUTHENTICATED = 1
@@ -69,6 +69,10 @@ class BiometricDeviceAuthentication(
fun cancelAuthentication() {
biometricPrompt.cancelAuthentication()
}
fun updatePromptInfo(promptInfo: PromptInfo) {
biometricPromptInfo = promptInfo
}
}
class BiometricDeviceLockContract : ActivityResultContract<String, Int>() {

View File

@@ -29,9 +29,9 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
/**
* Activity for changing a user's local encryption passphrase.
@@ -81,7 +81,7 @@ public class PassphraseChangeActivity extends PassphraseActivity {
this.okButton.setOnClickListener(new OkButtonClickListener());
this.cancelButton.setOnClickListener(new CancelButtonClickListener());
if (TextSecurePreferences.isPasswordDisabled(this)) {
if (SignalStore.settings().getPassphraseDisabled()) {
this.originalPassphrase.setVisibility(View.GONE);
} else {
this.originalPassphrase.setVisibility(View.VISIBLE);
@@ -97,7 +97,7 @@ public class PassphraseChangeActivity extends PassphraseActivity {
String passphrase = (newText == null ? "" : newText.toString());
String passphraseRepeat = (repeatText == null ? "" : repeatText.toString());
if (TextSecurePreferences.isPasswordDisabled(this)) {
if (SignalStore.settings().getPassphraseDisabled()) {
original = MasterSecretUtil.UNENCRYPTED_PASSPHRASE;
}
@@ -142,7 +142,7 @@ public class PassphraseChangeActivity extends PassphraseActivity {
protected MasterSecret doInBackground(String... params) {
try {
MasterSecret masterSecret = MasterSecretUtil.changeMasterSecretPassphrase(context, params[0], params[1]);
TextSecurePreferences.setPasswordDisabled(context, false);
SignalStore.settings().setPassphraseDisabled(false);
return masterSecret;

View File

@@ -20,7 +20,6 @@ import android.animation.Animator;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;
@@ -34,21 +33,18 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.BounceInterpolator;
import android.view.animation.TranslateAnimation;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
import androidx.core.content.ContextCompat;
import com.airbnb.lottie.LottieAnimationView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.ThreadUtil;
@@ -64,7 +60,7 @@ import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.DynamicIntroTheme;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.SupportEmailUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
import kotlin.Unit;
@@ -79,14 +75,14 @@ public class PassphrasePromptActivity extends PassphraseActivity {
private static final short AUTHENTICATE_REQUEST_CODE = 1007;
private static final String BUNDLE_ALREADY_SHOWN = "bundle_already_shown";
public static final String FROM_FOREGROUND = "from_foreground";
private static final int HELP_COUNT_THRESHOLD = 3;
private DynamicIntroTheme dynamicTheme = new DynamicIntroTheme();
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
private View passphraseAuthContainer;
private ImageView fingerprintPrompt;
private TextView lockScreenButton;
private View passphraseAuthContainer;
private LottieAnimationView unlockView;
private TextView lockScreenButton;
private LearnMoreTextView learnMoreText;
private EditText passphraseText;
private ImageButton showButton;
@@ -134,14 +130,11 @@ public class PassphrasePromptActivity extends PassphraseActivity {
setLockTypeVisibility();
if (TextSecurePreferences.isScreenLockEnabled(this) && !authenticated && !hadFailure) {
if (SignalStore.settings().getScreenLockEnabled() && !authenticated && !hadFailure) {
ThreadUtil.postToMain(resumeScreenLockRunnable);
}
hadFailure = false;
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_accent_primary), PorterDuff.Mode.SRC_IN);
}
@Override
@@ -174,9 +167,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
if (item.getItemId() == R.id.menu_submit_debug_logs) {
handleLogSubmit();
return true;
} else if (item.getItemId() == R.id.menu_contact_support) {
sendEmailToSupport();
return true;
}
return false;
@@ -193,7 +183,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
} else {
Log.w(TAG, "Authentication failed");
hadFailure = true;
incrementAttemptCountAndShowHelpIfNecessary();
showHelpDialog();
}
}
@@ -213,7 +203,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
passphraseText.setText("");
passphraseText.setError(
getString(R.string.PassphrasePromptActivity_invalid_passphrase_exclamation));
incrementAttemptCountAndShowHelpIfNecessary();
}
}
@@ -223,7 +212,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
setMasterSecret(masterSecret);
SignalStore.misc().setLockScreenAttemptCount(0);
} catch (InvalidPassphraseException e) {
throw new AssertionError(e);
}
@@ -251,8 +239,9 @@ public class PassphrasePromptActivity extends PassphraseActivity {
visibilityToggle = findViewById(R.id.button_toggle);
passphraseText = findViewById(R.id.passphrase_edit);
passphraseAuthContainer = findViewById(R.id.password_auth_container);
fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
unlockView = findViewById(R.id.unlock_view);
lockScreenButton = findViewById(R.id.lock_screen_button);
learnMoreText = findViewById(R.id.learn_more_text);
biometricManager = BiometricManager.from(this);
biometricPrompt = new BiometricPrompt(this, new BiometricAuthenticationListener());
BiometricPrompt.PromptInfo biometricPromptInfo = new BiometricPrompt.PromptInfo
@@ -276,34 +265,22 @@ public class PassphrasePromptActivity extends PassphraseActivity {
passphraseText.setImeActionLabel(getString(R.string.prompt_passphrase_activity__unlock),
EditorInfo.IME_ACTION_DONE);
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
lockScreenButton.setOnClickListener(v -> resumeScreenLock(true));
if (SignalStore.misc().getLockScreenAttemptCount() > HELP_COUNT_THRESHOLD) {
showHelpDialogAndResetAttemptCount(null);
}
}
private void setLockTypeVisibility() {
if (TextSecurePreferences.isScreenLockEnabled(this)) {
if (SignalStore.settings().getScreenLockEnabled()) {
passphraseAuthContainer.setVisibility(View.GONE);
fingerprintPrompt.setVisibility(biometricManager.canAuthenticate(BiometricDeviceAuthentication.BIOMETRIC_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS ? View.VISIBLE
: View.GONE);
unlockView.setVisibility(View.VISIBLE);
lockScreenButton.setVisibility(View.VISIBLE);
} else {
passphraseAuthContainer.setVisibility(View.VISIBLE);
fingerprintPrompt.setVisibility(View.GONE);
unlockView.setVisibility(View.GONE);
lockScreenButton.setVisibility(View.GONE);
}
}
private void resumeScreenLock(boolean force) {
if (incrementAttemptCountAndShowHelpIfNecessary(() -> resumeScreenLock(force))) {
return;
}
if (!biometricAuth.authenticate(getApplicationContext(), force, this::showConfirmDeviceCredentialIntent)) {
handleAuthenticated();
}
@@ -328,33 +305,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
return Unit.INSTANCE;
}
private boolean incrementAttemptCountAndShowHelpIfNecessary() {
return incrementAttemptCountAndShowHelpIfNecessary(null);
}
private boolean incrementAttemptCountAndShowHelpIfNecessary(Runnable onDismissed) {
SignalStore.misc().incrementLockScreenAttemptCount();
if (SignalStore.misc().getLockScreenAttemptCount() > HELP_COUNT_THRESHOLD) {
showHelpDialogAndResetAttemptCount(onDismissed);
return true;
}
return false;
}
private void showHelpDialogAndResetAttemptCount(@Nullable Runnable onDismissed) {
new MaterialAlertDialogBuilder(this)
.setMessage(R.string.PassphrasePromptActivity_help_prompt_body)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
SignalStore.misc().setLockScreenAttemptCount(0);
if (onDismissed != null) {
onDismissed.run();
}
})
.show();
}
private class PassphraseActionListener implements TextView.OnEditorActionListener {
@Override
public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent keyEvent) {
@@ -403,13 +353,31 @@ public class PassphrasePromptActivity extends PassphraseActivity {
System.gc();
}
private void showHelpDialog() {
lockScreenButton.setText(R.string.prompt_passphrase_activity__try_again);
learnMoreText.setVisibility(View.VISIBLE);
learnMoreText.setLearnMoreVisible(true);
learnMoreText.setLinkColor(ContextCompat.getColor(PassphrasePromptActivity.this, R.color.signal_colorPrimary));
learnMoreText.setOnClickListener(v ->
new MaterialAlertDialogBuilder(PassphrasePromptActivity.this)
.setTitle(R.string.prompt_passphrase_activity__unlock_signal)
.setMessage(R.string.prompt_passphrase_activity__screen_lock_is_on)
.setCancelable(true)
.setPositiveButton(android.R.string.ok, null)
.setNegativeButton(R.string.prompt_passphrase_activity__contact_support, (d,w) -> sendEmailToSupport())
.show()
);
}
private class BiometricAuthenticationListener extends BiometricPrompt.AuthenticationCallback {
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errorString) {
Log.w(TAG, "Authentication error: " + errorCode);
hadFailure = true;
incrementAttemptCountAndShowHelpIfNecessary();
showHelpDialog();
if (errorCode != BiometricPrompt.ERROR_CANCELED && errorCode != BiometricPrompt.ERROR_USER_CANCELED) {
onAuthenticationFailed();
@@ -419,41 +387,19 @@ public class PassphrasePromptActivity extends PassphraseActivity {
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
Log.i(TAG, "onAuthenticationSucceeded");
fingerprintPrompt.setImageResource(R.drawable.symbol_check_white_48);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
fingerprintPrompt.animate().setInterpolator(new BounceInterpolator()).scaleX(1.1f).scaleY(1.1f).setDuration(500).setListener(new AnimationCompleteListener() {
unlockView.addAnimatorListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
handleAuthenticated();
}
}).start();
});
unlockView.playAnimation();
}
@Override
public void onAuthenticationFailed() {
Log.w(TAG, "onAuthenticationFailed()");
fingerprintPrompt.setImageResource(R.drawable.symbol_x_white_48);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
TranslateAnimation shake = new TranslateAnimation(0, 30, 0, 0);
shake.setDuration(50);
shake.setRepeatCount(7);
shake.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_accent_primary), PorterDuff.Mode.SRC_IN);
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
fingerprintPrompt.startAnimation(shake);
showHelpDialog();
}
}
}

View File

@@ -287,8 +287,8 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
this.clearKeyReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive() for clear key event. PasswordDisabled: " + TextSecurePreferences.isPasswordDisabled(context) + ", ScreenLock: " + TextSecurePreferences.isScreenLockEnabled(context));
if (TextSecurePreferences.isScreenLockEnabled(context) || !TextSecurePreferences.isPasswordDisabled(context)) {
Log.i(TAG, "onReceive() for clear key event. PasswordDisabled: " + SignalStore.settings().getPassphraseDisabled() + ", ScreenLock: " + SignalStore.settings().getScreenLockEnabled());
if (SignalStore.settings().getScreenLockEnabled() || !SignalStore.settings().getPassphraseDisabled()) {
onMasterSecretCleared();
}
}

View File

@@ -109,8 +109,13 @@ class LearnMoreTextPreferenceViewHolder(itemView: View) : PreferenceViewHolder<L
class ClickPreferenceViewHolder(itemView: View) : PreferenceViewHolder<ClickPreference>(itemView) {
override fun bind(model: ClickPreference) {
super.bind(model)
itemView.setOnClickListener { model.onClick() }
itemView.setOnLongClickListener { model.onLongClick?.invoke() ?: false }
if (!itemView.isEnabled && model.onDisabledClicked != null) {
itemView.isEnabled = true
itemView.setOnClickListener { model.onDisabledClicked() }
} else {
itemView.setOnClickListener { model.onClick() }
itemView.setOnLongClickListener { model.onLongClick?.invoke() ?: false }
}
}
}

View File

@@ -21,6 +21,7 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.navArgs
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.BiometricDeviceAuthentication
import org.thoughtcrime.securesms.BiometricDeviceLockContract
@@ -36,9 +37,9 @@ import org.thoughtcrime.securesms.components.settings.PreferenceModel
import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.ConversationUtil
import org.thoughtcrime.securesms.util.ExpirationUtil
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.SpanUtil
@@ -197,7 +198,7 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
KeyCachingService.getMasterSecret(context),
MasterSecretUtil.UNENCRYPTED_PASSPHRASE
)
TextSecurePreferences.setPasswordDisabled(activity, true)
SignalStore.settings.passphraseDisabled = true
val intent = Intent(activity, KeyCachingService::class.java)
intent.action = KeyCachingService.DISABLE_ACTION
requireActivity().startService(intent)
@@ -249,33 +250,21 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
} else {
val isKeyguardSecure = ServiceUtil.getKeyguardManager(requireContext()).isKeyguardSecure
switchPref(
title = DSLSettingsText.from(R.string.preferences_app_protection__screen_lock),
summary = DSLSettingsText.from(R.string.preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint),
isChecked = state.screenLock && isKeyguardSecure,
isEnabled = isKeyguardSecure,
onClick = {
viewModel.setScreenLockEnabled(!state.screenLock)
val intent = Intent(requireContext(), KeyCachingService::class.java)
intent.action = KeyCachingService.LOCK_TOGGLED_EVENT
requireContext().startService(intent)
ConversationUtil.refreshRecipientShortcuts()
}
)
clickPref(
title = DSLSettingsText.from(R.string.preferences_app_protection__screen_lock_inactivity_timeout),
summary = DSLSettingsText.from(getScreenLockInactivityTimeoutSummary(state.screenLockActivityTimeout)),
isEnabled = isKeyguardSecure && state.screenLock,
title = DSLSettingsText.from(R.string.preferences_app_protection__screen_lock),
summary = DSLSettingsText.from(getScreenLockInactivityTimeoutSummary(isKeyguardSecure && state.screenLock, state.screenLockActivityTimeout)),
onClick = {
childFragmentManager.clearFragmentResult(TimeDurationPickerDialog.RESULT_DURATION)
childFragmentManager.clearFragmentResultListener(TimeDurationPickerDialog.RESULT_DURATION)
childFragmentManager.setFragmentResultListener(TimeDurationPickerDialog.RESULT_DURATION, this@PrivacySettingsFragment) { _, bundle ->
viewModel.setScreenLockTimeout(bundle.getLong(TimeDurationPickerDialog.RESULT_KEY_DURATION_MILLISECONDS).milliseconds.inWholeSeconds)
}
TimeDurationPickerDialog.create(state.screenLockActivityTimeout.seconds).show(childFragmentManager, null)
Navigation.findNavController(requireView()).safeNavigate(R.id.action_privacySettingsFragment_to_screenLockSettingsFragment)
},
isEnabled = isKeyguardSecure,
onDisabledClicked = {
Snackbar
.make(
requireView(),
resources.getString(R.string.preferences_app_protection__to_use_screen_lock),
Snackbar.LENGTH_LONG
)
.show()
}
)
}
@@ -362,9 +351,11 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
}
}
private fun getScreenLockInactivityTimeoutSummary(timeoutSeconds: Long): String {
return if (timeoutSeconds <= 0) {
getString(R.string.AppProtectionPreferenceFragment_none)
private fun getScreenLockInactivityTimeoutSummary(enabledScreenLock: Boolean, timeoutSeconds: Long): String {
return if (!enabledScreenLock) {
getString(R.string.ScreenLockSettingsFragment__off)
} else if (timeoutSeconds == 0L) {
getString(R.string.ScreenLockSettingsFragment__immediately)
} else {
ExpirationUtil.getExpirationDisplayValue(requireContext(), timeoutSeconds.toInt())
}

View File

@@ -37,16 +37,6 @@ class PrivacySettingsViewModel(
refresh()
}
fun setScreenLockEnabled(enabled: Boolean) {
sharedPreferences.edit().putBoolean(TextSecurePreferences.SCREEN_LOCK, enabled).apply()
refresh()
}
fun setScreenLockTimeout(seconds: Long) {
TextSecurePreferences.setScreenLockTimeout(AppDependencies.application, seconds)
refresh()
}
fun setScreenSecurityEnabled(enabled: Boolean) {
sharedPreferences.edit().putBoolean(TextSecurePreferences.SCREEN_SECURITY_PREF, enabled).apply()
refresh()
@@ -63,12 +53,12 @@ class PrivacySettingsViewModel(
}
fun setObsoletePasswordTimeoutEnabled(enabled: Boolean) {
sharedPreferences.edit().putBoolean(TextSecurePreferences.PASSPHRASE_TIMEOUT_PREF, enabled).apply()
SignalStore.settings.passphraseTimeoutEnabled = enabled
refresh()
}
fun setObsoletePasswordTimeout(minutes: Int) {
TextSecurePreferences.setPassphraseTimeoutInterval(AppDependencies.application, minutes)
SignalStore.settings.passphraseTimeout = minutes
refresh()
}
@@ -81,14 +71,14 @@ class PrivacySettingsViewModel(
blockedCount = 0,
readReceipts = TextSecurePreferences.isReadReceiptsEnabled(AppDependencies.application),
typingIndicators = TextSecurePreferences.isTypingIndicatorsEnabled(AppDependencies.application),
screenLock = TextSecurePreferences.isScreenLockEnabled(AppDependencies.application),
screenLockActivityTimeout = TextSecurePreferences.getScreenLockTimeout(AppDependencies.application),
screenLock = SignalStore.settings.screenLockEnabled,
screenLockActivityTimeout = SignalStore.settings.screenLockTimeout,
screenSecurity = TextSecurePreferences.isScreenSecurityEnabled(AppDependencies.application),
incognitoKeyboard = TextSecurePreferences.isIncognitoKeyboardEnabled(AppDependencies.application),
paymentLock = SignalStore.payments.paymentLock,
isObsoletePasswordEnabled = !TextSecurePreferences.isPasswordDisabled(AppDependencies.application),
isObsoletePasswordTimeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(AppDependencies.application),
obsoletePasswordTimeout = TextSecurePreferences.getPassphraseTimeoutInterval(AppDependencies.application),
isObsoletePasswordEnabled = !SignalStore.settings.passphraseDisabled,
isObsoletePasswordTimeoutEnabled = SignalStore.settings.passphraseTimeoutEnabled,
obsoletePasswordTimeout = SignalStore.settings.passphraseTimeout,
universalExpireTimer = SignalStore.settings.universalExpireTimer
)
}

View File

@@ -65,6 +65,12 @@ class CustomExpireTimerSelectorView @JvmOverloads constructor(
valuePicker.maxValue = timerUnit.maxValue
}
fun setUnits(minValue: Int, maxValue: Int, timeUnitRes: Int) {
unitPicker.minValue = minValue
unitPicker.maxValue = maxValue
unitPicker.displayedValues = context.resources.getStringArray(timeUnitRes)
}
private enum class TimerUnit(val minValue: Int, val maxValue: Int, val valueMultiplier: Long) {
SECONDS(1, 59, TimeUnit.SECONDS.toSeconds(1)),
MINUTES(1, 59, TimeUnit.MINUTES.toSeconds(1)),

View File

@@ -0,0 +1,37 @@
package org.thoughtcrime.securesms.components.settings.app.privacy.screenlock
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.app.privacy.expire.CustomExpireTimerSelectorView
/**
* Dialog for selecting a custom timer value when setting the screen lock timeout.
*/
class CustomScreenLockTimerSelectDialog : DialogFragment() {
private val viewModel: ScreenLockSettingsViewModel by activityViewModels()
private lateinit var selector: CustomExpireTimerSelectorView
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialogView: View = LayoutInflater.from(context).inflate(R.layout.custom_expire_timer_select_dialog, null, false)
selector = dialogView.findViewById(R.id.custom_expire_timer_select_dialog_selector)
selector.setUnits(1, 3, R.array.CustomScreenLockTimerSelectorView__unit_labels)
selector.setTimer(viewModel.state.value.screenLockActivityTimeout.toInt())
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.ExpireTimerSettingsFragment__custom_time)
.setView(dialogView)
.setPositiveButton(R.string.ExpireTimerSettingsFragment__set) { _, _ ->
viewModel.setScreenLockTimeout(selector.getTimer().toLong())
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
}

View File

@@ -0,0 +1,284 @@
package org.thoughtcrime.securesms.components.settings.app.privacy.screenlock
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.fragment.app.activityViewModels
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController
import org.signal.core.ui.Previews
import org.signal.core.ui.Scaffolds
import org.signal.core.ui.SignalPreview
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.BiometricDeviceAuthentication
import org.thoughtcrime.securesms.BiometricDeviceLockContract
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.util.ConversationUtil
import org.thoughtcrime.securesms.util.ExpirationUtil
import org.thoughtcrime.securesms.util.navigation.safeNavigate
/**
* Fragment that allows user to turn on screen lock and set a timer to lock
*/
class ScreenLockSettingsFragment : ComposeFragment() {
companion object {
private val TAG = Log.tag(ScreenLockSettingsFragment::class)
}
private val viewModel: ScreenLockSettingsViewModel by activityViewModels()
private lateinit var biometricAuth: BiometricDeviceAuthentication
private lateinit var biometricDeviceLockLauncher: ActivityResultLauncher<String>
private lateinit var disableLockPromptInfo: BiometricPrompt.PromptInfo
private lateinit var enableLockPromptInfo: BiometricPrompt.PromptInfo
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
biometricDeviceLockLauncher = registerForActivityResult(BiometricDeviceLockContract()) { result: Int ->
if (result == BiometricDeviceAuthentication.AUTHENTICATED) {
toggleScreenLock()
}
}
enableLockPromptInfo = BiometricPrompt.PromptInfo.Builder()
.setAllowedAuthenticators(BiometricDeviceAuthentication.ALLOWED_AUTHENTICATORS)
.setTitle(requireContext().getString(R.string.ScreenLockSettingsFragment__use_signal_screen_lock))
.setConfirmationRequired(true)
.build()
disableLockPromptInfo = BiometricPrompt.PromptInfo.Builder()
.setAllowedAuthenticators(BiometricDeviceAuthentication.ALLOWED_AUTHENTICATORS)
.setTitle(requireContext().getString(R.string.ScreenLockSettingsFragment__turn_off_signal_lock))
.setConfirmationRequired(true)
.build()
biometricAuth = BiometricDeviceAuthentication(
BiometricManager.from(requireActivity()),
BiometricPrompt(requireActivity(), BiometricAuthenticationListener()),
enableLockPromptInfo
)
}
override fun onPause() {
super.onPause()
biometricAuth.cancelAuthentication()
}
@Composable
override fun FragmentContent() {
val state by viewModel.state.collectAsState()
val navController: NavController by remember { mutableStateOf(findNavController()) }
Scaffolds.Settings(
title = stringResource(id = R.string.preferences_app_protection__screen_lock),
onNavigationClick = { navController.popBackStack() },
navigationIconPainter = painterResource(id = R.drawable.ic_arrow_left_24),
navigationContentDescription = stringResource(id = R.string.Material3SearchToolbar__close)
) { contentPadding: PaddingValues ->
ScreenLockScreen(
state = state,
onChecked = { checked ->
if (biometricAuth.canAuthenticate() && !checked) {
biometricAuth.updatePromptInfo(disableLockPromptInfo)
biometricAuth.authenticate(requireContext(), true) {
biometricDeviceLockLauncher.launch(getString(R.string.ScreenLockSettingsFragment__turn_off_signal_lock))
}
} else if (biometricAuth.canAuthenticate() && checked) {
biometricAuth.updatePromptInfo(enableLockPromptInfo)
biometricAuth.authenticate(requireContext(), true) {
biometricDeviceLockLauncher.launch(getString(R.string.ScreenLockSettingsFragment__use_screen_lock))
}
}
},
onTimeClicked = viewModel::setScreenLockTimeout,
onCustomTimeClicked = { navController.safeNavigate(R.id.action_screenLockSettingsFragment_to_customScreenLockTimerSelectDialog) },
modifier = Modifier.padding(contentPadding)
)
}
}
private fun toggleScreenLock() {
viewModel.toggleScreenLock()
val intent = Intent(requireContext(), KeyCachingService::class.java)
intent.action = KeyCachingService.LOCK_TOGGLED_EVENT
requireContext().startService(intent)
ConversationUtil.refreshRecipientShortcuts()
}
private inner class BiometricAuthenticationListener : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errorString: CharSequence) {
Log.w(TAG, "Authentication error: $errorCode")
onAuthenticationFailed()
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
Log.i(TAG, "Authentication succeeded")
toggleScreenLock()
}
override fun onAuthenticationFailed() {
Log.w(TAG, "Unable to authenticate")
}
}
}
@Composable
fun ScreenLockScreen(
state: ScreenLockSettingsState,
onChecked: (Boolean) -> Unit,
onTimeClicked: (Long) -> Unit,
onCustomTimeClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Column(modifier = modifier.verticalScroll(rememberScrollState())) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
painter = painterResource(R.drawable.ic_screen_lock),
contentDescription = null,
tint = Color.Unspecified,
modifier = Modifier.padding(top = 24.dp, bottom = 24.dp)
)
Text(
text = stringResource(id = R.string.ScreenLockSettingsFragment__your_android_device),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(start = 40.dp, end = 40.dp, bottom = 24.dp)
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(horizontal = 24.dp)
.background(color = MaterialTheme.colorScheme.surfaceVariant, shape = RoundedCornerShape(24.dp))
.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 12.dp)
) {
Text(stringResource(id = R.string.ScreenLockSettingsFragment__use_screen_lock))
Spacer(Modifier.weight(1f))
Switch(checked = state.screenLock, onCheckedChange = onChecked)
}
}
if (state.screenLock) {
val labels: List<String> = LocalContext.current.resources.getStringArray(R.array.ScreenLockSettingsFragment__labels).toList()
val values: List<Long> = LocalContext.current.resources.getIntArray(R.array.ScreenLockSettingsFragment__values).map { it.toLong() }
Text(
stringResource(id = R.string.ScreenLockSettingsFragment__start_screen_lock),
style = MaterialTheme.typography.titleSmall,
modifier = Modifier.padding(top = 24.dp, bottom = 16.dp, start = 24.dp)
)
Column(Modifier.selectableGroup()) {
var isCustomTime = true
labels.zip(values).forEach { (label, seconds) ->
val isSelected = seconds == state.screenLockActivityTimeout
Row(
Modifier
.fillMaxWidth()
.defaultMinSize(minHeight = 56.dp)
.selectable(
selected = isSelected,
onClick = { onTimeClicked(seconds) },
role = Role.RadioButton
)
.padding(horizontal = 24.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(selected = isSelected, onClick = null)
Text(
text = label,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(start = 16.dp)
)
isCustomTime = isCustomTime && !isSelected
}
}
Row(
Modifier
.fillMaxWidth()
.height(56.dp)
.selectable(
selected = isCustomTime,
onClick = onCustomTimeClicked,
role = Role.RadioButton
)
.padding(horizontal = 24.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(selected = isCustomTime, onClick = null)
Column(modifier = Modifier.padding(start = 16.dp)) {
Text(
text = stringResource(id = R.string.ScreenLockSettingsFragment__custom_time),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface
)
if (isCustomTime && state.screenLockActivityTimeout > 0) {
Text(
text = ExpirationUtil.getExpirationDisplayValue(LocalContext.current, state.screenLockActivityTimeout.toInt()),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
}
}
}
@SignalPreview
@Composable
fun ScreenLockScreenPreview() {
Previews.Preview {
ScreenLockScreen(
state = ScreenLockSettingsState(true, 60),
onChecked = {},
onTimeClicked = {},
onCustomTimeClicked = {}
)
}
}

View File

@@ -0,0 +1,9 @@
package org.thoughtcrime.securesms.components.settings.app.privacy.screenlock
/**
* Information about the screen lock state. Used in [ScreenLockSettingsViewModel].
*/
data class ScreenLockSettingsState(
val screenLock: Boolean = false,
val screenLockActivityTimeout: Long = 0L
)

View File

@@ -0,0 +1,42 @@
package org.thoughtcrime.securesms.components.settings.app.privacy.screenlock
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import org.thoughtcrime.securesms.keyvalue.SignalStore
/**
* Maintains the state of the [ScreenLockSettingsFragment]
*/
class ScreenLockSettingsViewModel : ViewModel() {
private val _state = MutableStateFlow(getState())
val state = _state.asStateFlow()
fun toggleScreenLock() {
val enabled = !_state.value.screenLock
SignalStore.settings.screenLockEnabled = enabled
_state.update {
it.copy(
screenLock = enabled
)
}
}
fun setScreenLockTimeout(seconds: Long) {
SignalStore.settings.screenLockTimeout = seconds
_state.update {
it.copy(
screenLockActivityTimeout = seconds
)
}
}
private fun getState(): ScreenLockSettingsState {
return ScreenLockSettingsState(
screenLock = SignalStore.settings.screenLockEnabled,
screenLockActivityTimeout = SignalStore.settings.screenLockTimeout
)
}
}

View File

@@ -105,9 +105,10 @@ class DSLConfiguration {
iconEnd: DSLSettingsIcon? = null,
isEnabled: Boolean = true,
onClick: () -> Unit,
onLongClick: (() -> Boolean)? = null
onLongClick: (() -> Boolean)? = null,
onDisabledClicked: () -> Unit = {}
) {
val preference = ClickPreference(title, summary, icon, iconEnd, isEnabled, onClick, onLongClick)
val preference = ClickPreference(title, summary, icon, iconEnd, isEnabled, onClick, onLongClick, onDisabledClicked)
children.add(preference)
}
@@ -344,7 +345,8 @@ class ClickPreference(
override val iconEnd: DSLSettingsIcon? = null,
override val isEnabled: Boolean = true,
val onClick: () -> Unit,
val onLongClick: (() -> Boolean)? = null
val onLongClick: (() -> Boolean)? = null,
val onDisabledClicked: () -> Unit = {}
) : PreferenceModel<ClickPreference>()
class LongClickPreference(

View File

@@ -568,7 +568,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
@Override
public void onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.menu_clear_passphrase).setVisible(!TextSecurePreferences.isPasswordDisabled(requireContext()));
menu.findItem(R.id.menu_clear_passphrase).setVisible(!SignalStore.settings().getPassphraseDisabled());
ConversationFilterRequest request = viewModel.getConversationFilterRequest();
boolean isChatFilterEnabled = request != null && request.getFilter() == ConversationFilter.UNREAD;

View File

@@ -5,12 +5,12 @@ import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.JsonJobData
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.transport.RetryLaterException
import org.thoughtcrime.securesms.util.ConversationUtil
import org.thoughtcrime.securesms.util.ConversationUtil.Direction
import org.thoughtcrime.securesms.util.TextSecurePreferences
import kotlin.time.Duration.Companion.seconds
/**
@@ -65,7 +65,7 @@ class ConversationShortcutRankingUpdateJob private constructor(
override fun getFactoryKey() = KEY
override fun onRun() {
if (TextSecurePreferences.isScreenLockEnabled(context)) {
if (SignalStore.settings.screenLockEnabled) {
Log.i(TAG, "Screen lock enabled. Clearing shortcuts.")
ConversationUtil.clearAllShortcuts(context)
return

View File

@@ -9,10 +9,10 @@ import org.thoughtcrime.securesms.database.ThreadTable;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.util.ConversationUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.ArrayList;
import java.util.List;
@@ -58,7 +58,7 @@ public class ConversationShortcutUpdateJob extends BaseJob {
@Override
protected void onRun() throws Exception {
if (TextSecurePreferences.isScreenLockEnabled(context)) {
if (SignalStore.settings().getScreenLockEnabled()) {
Log.i(TAG, "Screen lock enabled. Clearing shortcuts.");
ConversationUtil.clearAllShortcuts(context);
return;

View File

@@ -67,6 +67,11 @@ public final class SettingsValues extends SignalStoreValues {
private static final String KEEP_MUTED_CHATS_ARCHIVED = "settings.keepMutedChatsArchived";
private static final String USE_COMPACT_NAVIGATION_BAR = "settings.useCompactNavigationBar";
private static final String THREAD_TRIM_SYNC_TO_LINKED_DEVICES = "settings.storage.syncThreadTrimDeletes";
private static final String PASSPHRASE_DISABLED = "settings.passphrase.disabled";
private static final String PASSPHRASE_TIMEOUT_ENABLED = "settings.passphrase.timeout.enabled";
private static final String PASSPHRASE_TIMEOUT = "settings.passphrase.timeout";
private static final String SCREEN_LOCK_ENABLED = "settings.screen.lock.enabled";
private static final String SCREEN_LOCK_TIMEOUT = "settings.screen.lock.timeout";
public static final int BACKUP_DEFAULT_HOUR = 2;
public static final int BACKUP_DEFAULT_MINUTE = 0;
@@ -75,6 +80,20 @@ public final class SettingsValues extends SignalStoreValues {
SettingsValues(@NonNull KeyValueStore store) {
super(store);
if (!store.containsKey(SCREEN_LOCK_ENABLED)) {
migrateFromSharedPrefsV1(AppDependencies.getApplication());
}
}
private void migrateFromSharedPrefsV1(@NonNull Context context) {
Log.i(TAG, "[V1] Migrating screen lock values from shared prefs.");
putBoolean(PASSPHRASE_DISABLED, TextSecurePreferences.getBooleanPreference(context, "pref_disable_passphrase", true));
putBoolean(PASSPHRASE_TIMEOUT_ENABLED, TextSecurePreferences.getBooleanPreference(context, "pref_timeout_passphrase", false));
putInteger(PASSPHRASE_TIMEOUT, TextSecurePreferences.getIntegerPreference(context, "pref_timeout_interval", 5 * 60));
putBoolean(SCREEN_LOCK_ENABLED, TextSecurePreferences.getBooleanPreference(context, "pref_android_screen_lock", false));
putLong(SCREEN_LOCK_TIMEOUT, TextSecurePreferences.getLongPreference(context, "pref_android_screen_lock_timeout", 0));
}
@Override
@@ -121,7 +140,12 @@ public final class SettingsValues extends SignalStoreValues {
SENT_MEDIA_QUALITY,
KEEP_MUTED_CHATS_ARCHIVED,
USE_COMPACT_NAVIGATION_BAR,
THREAD_TRIM_SYNC_TO_LINKED_DEVICES);
THREAD_TRIM_SYNC_TO_LINKED_DEVICES,
PASSPHRASE_DISABLED,
PASSPHRASE_TIMEOUT_ENABLED,
PASSPHRASE_TIMEOUT,
SCREEN_LOCK_ENABLED,
SCREEN_LOCK_TIMEOUT);
}
public @NonNull LiveData<String> getOnConfigurationSettingChanged() {
@@ -461,6 +485,46 @@ public final class SettingsValues extends SignalStoreValues {
return getBoolean(USE_COMPACT_NAVIGATION_BAR, false);
}
public void setPassphraseDisabled(boolean disabled) {
putBoolean(PASSPHRASE_DISABLED, disabled);
}
public boolean getPassphraseDisabled() {
return getBoolean(PASSPHRASE_DISABLED, true);
}
public void setPassphraseTimeoutEnabled(boolean enabled) {
putBoolean(PASSPHRASE_TIMEOUT_ENABLED, enabled);
}
public boolean getPassphraseTimeoutEnabled() {
return getBoolean(PASSPHRASE_TIMEOUT_ENABLED, false);
}
public void setPassphraseTimeout(int minutes) {
putLong(PASSPHRASE_TIMEOUT, minutes);
}
public int getPassphraseTimeout() {
return getInteger(PASSPHRASE_TIMEOUT, 0);
}
public void setScreenLockEnabled(boolean enabled) {
putBoolean(SCREEN_LOCK_ENABLED, enabled);
}
public boolean getScreenLockEnabled() {
return getBoolean(SCREEN_LOCK_ENABLED, false);
}
public void setScreenLockTimeout(long seconds) {
putLong(SCREEN_LOCK_TIMEOUT, seconds);
}
public long getScreenLockTimeout() {
return getLong(SCREEN_LOCK_TIMEOUT, 0);
}
private @Nullable Uri getUri(@NonNull String key) {
String uri = getString(key, "");

View File

@@ -19,9 +19,9 @@ final class LogSectionKeyPreferences implements LogSection {
@Override
public @NonNull CharSequence getContent(@NonNull Context context) {
return new StringBuilder().append("Screen Lock : ").append(TextSecurePreferences.isScreenLockEnabled(context)).append("\n")
.append("Screen Lock Timeout : ").append(TextSecurePreferences.getScreenLockTimeout(context)).append("\n")
.append("Password Disabled : ").append(TextSecurePreferences.isPasswordDisabled(context)).append("\n")
return new StringBuilder().append("Screen Lock : ").append(SignalStore.settings().getScreenLockEnabled()).append("\n")
.append("Screen Lock Timeout : ").append(SignalStore.settings().getScreenLockTimeout()).append("\n")
.append("Password Disabled : ").append(SignalStore.settings().getPassphraseDisabled()).append("\n")
.append("Prefer Contact Photos : ").append(SignalStore.settings().isPreferSystemContactPhotos()).append("\n")
.append("Call Data Mode : ").append(SignalStore.settings().getCallDataMode()).append("\n")
.append("Media Quality : ").append(SignalStore.settings().getSentMediaQuality()).append("\n")

View File

@@ -32,6 +32,7 @@ import android.os.SystemClock;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.BuildConfig;
@@ -42,11 +43,11 @@ import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
@@ -80,17 +81,17 @@ public class KeyCachingService extends Service {
public KeyCachingService() {}
public static synchronized boolean isLocked(Context context) {
boolean locked = masterSecret == null && (!TextSecurePreferences.isPasswordDisabled(context) || TextSecurePreferences.isScreenLockEnabled(context));
boolean locked = masterSecret == null && (!SignalStore.settings().getPassphraseDisabled() || SignalStore.settings().getScreenLockEnabled());
if (locked) {
Log.d(TAG, "Locked! PasswordDisabled: " + TextSecurePreferences.isPasswordDisabled(context) + ", ScreenLock: " + TextSecurePreferences.isScreenLockEnabled(context));
Log.d(TAG, "Locked! PasswordDisabled: " + SignalStore.settings().getPassphraseDisabled() + ", ScreenLock: " + SignalStore.settings().getScreenLockEnabled());
}
return locked;
}
public static synchronized @Nullable MasterSecret getMasterSecret(Context context) {
if (masterSecret == null && (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context))) {
if (masterSecret == null && (SignalStore.settings().getPassphraseDisabled() && !SignalStore.settings().getScreenLockEnabled())) {
try {
return MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
} catch (InvalidPassphraseException e) {
@@ -102,7 +103,7 @@ public class KeyCachingService extends Service {
}
public static void onAppForegrounded(@NonNull Context context) {
if (TextSecurePreferences.isScreenLockEnabled(context) || !TextSecurePreferences.isPasswordDisabled(context)) {
if (SignalStore.settings().getScreenLockEnabled() || !SignalStore.settings().getPassphraseDisabled()) {
ServiceUtil.getAlarmManager(context).cancel(buildExpirationPendingIntent(context));
}
}
@@ -155,7 +156,7 @@ public class KeyCachingService extends Service {
Log.i(TAG, "onCreate()");
super.onCreate();
if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) {
if (SignalStore.settings().getPassphraseDisabled() && !SignalStore.settings().getScreenLockEnabled()) {
try {
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
setMasterSecret(masterSecret);
@@ -215,8 +216,8 @@ public class KeyCachingService extends Service {
}
private void handleDisableService() {
if (TextSecurePreferences.isPasswordDisabled(this) &&
!TextSecurePreferences.isScreenLockEnabled(this))
if (SignalStore.settings().getPassphraseDisabled() &&
!SignalStore.settings().getScreenLockEnabled())
{
stopForeground(true);
}
@@ -231,33 +232,40 @@ public class KeyCachingService extends Service {
boolean appVisible = AppDependencies.getAppForegroundObserver().isForegrounded();
boolean secretSet = KeyCachingService.masterSecret != null;
boolean timeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(context);
boolean passLockActive = timeoutEnabled && !TextSecurePreferences.isPasswordDisabled(context);
boolean timeoutEnabled = SignalStore.settings().getPassphraseTimeoutEnabled();
boolean passLockActive = timeoutEnabled && !SignalStore.settings().getPassphraseDisabled();
long screenTimeout = TextSecurePreferences.getScreenLockTimeout(context);
boolean screenLockActive = screenTimeout >= 60 && TextSecurePreferences.isScreenLockEnabled(context);
long screenTimeout = SignalStore.settings().getScreenLockTimeout();
boolean screenLockActive = SignalStore.settings().getScreenLockEnabled();
boolean immediateScreenLock = screenTimeout == 0 && screenLockActive;
if (!appVisible && secretSet && (passLockActive || screenLockActive)) {
long passphraseTimeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(context);
long screenLockTimeoutSeconds = TextSecurePreferences.getScreenLockTimeout(context);
if (immediateScreenLock) {
Log.i(TAG, "Starting immediate screen lock");
Intent intent = new Intent(PASSPHRASE_EXPIRED_EVENT, null, context, KeyCachingService.class);
context.startService(intent);
} else {
long passphraseTimeoutMinutes = SignalStore.settings().getPassphraseTimeout();
long screenLockTimeoutSeconds = SignalStore.settings().getScreenLockTimeout();
long timeoutMillis;
long timeoutMillis;
if (!TextSecurePreferences.isPasswordDisabled(context)) timeoutMillis = TimeUnit.MINUTES.toMillis(passphraseTimeoutMinutes);
else timeoutMillis = TimeUnit.SECONDS.toMillis(screenLockTimeoutSeconds);
if (!SignalStore.settings().getPassphraseDisabled()) timeoutMillis = TimeUnit.MINUTES.toMillis(passphraseTimeoutMinutes);
else timeoutMillis = TimeUnit.SECONDS.toMillis(screenLockTimeoutSeconds);
Log.i(TAG, "Starting timeout: " + timeoutMillis);
Log.i(TAG, "Starting timeout: " + timeoutMillis);
AlarmManager alarmManager = ServiceUtil.getAlarmManager(context);
PendingIntent expirationIntent = buildExpirationPendingIntent(context);
AlarmManager alarmManager = ServiceUtil.getAlarmManager(context);
PendingIntent expirationIntent = buildExpirationPendingIntent(context);
alarmManager.cancel(expirationIntent);
alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + timeoutMillis, expirationIntent);
alarmManager.cancel(expirationIntent);
alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + timeoutMillis, expirationIntent);
}
}
}
private void foregroundService() {
if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) {
if (SignalStore.settings().getPassphraseDisabled() && !SignalStore.settings().getScreenLockEnabled()) {
stopForeground(true);
return;
}
@@ -267,7 +275,8 @@ public class KeyCachingService extends Service {
builder.setContentTitle(getString(R.string.KeyCachingService_passphrase_cached));
builder.setContentText(getString(R.string.KeyCachingService_signal_passphrase_cached));
builder.setSmallIcon(R.drawable.icon_cached);
builder.setSmallIcon(R.drawable.ic_notification_unlocked);
builder.setColor(ContextCompat.getColor(this, R.color.signal_light_colorSecondary));
builder.setWhen(0);
builder.setPriority(Notification.PRIORITY_MIN);
builder.setOngoing(true);

View File

@@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
/**
* Respond to a PanicKit trigger Intent by locking the app. PanicKit provides a
@@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
*/
class PanicResponderListener : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val passwordEnabled = !TextSecurePreferences.isPasswordDisabled(context)
val passwordEnabled = !SignalStore.settings.passphraseDisabled
val keyguardSecure = ServiceUtil.getKeyguardManager(context).isKeyguardSecure
val intentAction = intent.action
if ((passwordEnabled || keyguardSecure) && "info.guardianproject.panic.action.TRIGGER" == intentAction) {