Update screen lock.
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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, "");
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
BIN
app/src/main/res/drawable-hdpi/ic_notification_unlocked.png
Normal file
|
After Width: | Height: | Size: 813 B |
|
Before Width: | Height: | Size: 296 B |
BIN
app/src/main/res/drawable-mdpi/ic_notification_unlocked.png
Normal file
|
After Width: | Height: | Size: 592 B |
|
Before Width: | Height: | Size: 212 B |
BIN
app/src/main/res/drawable-xhdpi/ic_notification_unlocked.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 346 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_notification_unlocked.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 544 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_notification_unlocked.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 786 B |
34
app/src/main/res/drawable/ic_screen_lock.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="72dp"
|
||||
android:height="72dp"
|
||||
android:viewportWidth="72"
|
||||
android:viewportHeight="72">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M18,0L54,0A18,18 0,0 1,72 18L72,54A18,18 0,0 1,54 72L18,72A18,18 0,0 1,0 54L0,18A18,18 0,0 1,18 0z"/>
|
||||
<path
|
||||
android:pathData="M16,0L56,0A16,16 0,0 1,72 16L72,56A16,16 0,0 1,56 72L16,72A16,16 0,0 1,0 56L0,16A16,16 0,0 1,16 0z"
|
||||
android:fillColor="#D2D8FE"/>
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M16,0L56,0A16,16 0,0 1,72 16L72,56A16,16 0,0 1,56 72L16,72A16,16 0,0 1,0 56L0,16A16,16 0,0 1,16 0z"/>
|
||||
<path
|
||||
android:pathData="M70.973,33.982C71.634,34.534 72,35.357 72,36.217V65.684C72,68.998 69.314,71.684 66,71.684H6C2.686,71.684 0,68.998 0,65.684V37.984C0,37.183 0.317,36.412 0.901,35.863C10.002,27.319 22.811,22 37,22C50.117,22 62.054,26.546 70.973,33.982Z"
|
||||
android:fillColor="#C1C7FE"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M24,11L48,11A8,8 0,0 1,56 19L56,83A8,8 0,0 1,48 91L24,91A8,8 0,0 1,16 83L16,19A8,8 0,0 1,24 11z"
|
||||
android:strokeWidth="2.75"
|
||||
android:fillColor="#E3E8FE"
|
||||
android:strokeColor="#020BAC"/>
|
||||
<path
|
||||
android:pathData="M27.056,33.97C27.056,29.016 31.06,25 36,25C40.94,25 44.944,29.016 44.944,33.97V38.058C45.81,38.535 46.514,39.262 46.963,40.146C47.269,40.749 47.39,41.388 47.446,42.078C47.5,42.741 47.5,43.554 47.5,44.534V50.497C47.5,51.477 47.5,52.29 47.446,52.953C47.39,53.643 47.269,54.282 46.963,54.885C46.49,55.815 45.736,56.571 44.809,57.045C44.208,57.352 43.571,57.473 42.883,57.529C42.222,57.583 41.411,57.583 40.433,57.583H31.567C30.589,57.583 29.778,57.583 29.117,57.529C28.429,57.473 27.792,57.352 27.191,57.045C26.264,56.571 25.51,55.815 25.037,54.885C24.731,54.282 24.61,53.643 24.554,52.953C24.5,52.29 24.5,51.477 24.5,50.497V44.534C24.5,43.554 24.5,42.741 24.554,42.078C24.61,41.388 24.731,40.749 25.037,40.146C25.486,39.262 26.19,38.535 27.056,38.058V33.97ZM42.389,33.97V37.472C41.829,37.447 41.18,37.448 40.433,37.448H31.567C30.82,37.448 30.171,37.447 29.611,37.472V33.97C29.611,30.431 32.472,27.563 36,27.563C39.528,27.563 42.389,30.431 42.389,33.97Z"
|
||||
android:fillColor="#99A3EC"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M42.389,37.47C42.347,37.469 42.304,37.467 42.261,37.465C41.731,37.446 41.124,37.446 40.433,37.446H31.567C30.876,37.446 30.269,37.446 29.739,37.465C29.696,37.467 29.653,37.469 29.611,37.47V33.969C29.611,30.431 32.472,27.563 36,27.563C39.528,27.563 42.389,30.431 42.389,33.969V37.47ZM26.928,38.13C26.121,38.606 25.464,39.304 25.037,40.145C24.731,40.747 24.61,41.386 24.554,42.076C24.5,42.739 24.5,43.552 24.5,44.532V50.494C24.5,51.475 24.5,52.287 24.554,52.95C24.61,53.64 24.731,54.279 25.037,54.882C25.51,55.812 26.264,56.568 27.191,57.041C27.792,57.348 28.429,57.47 29.117,57.526C29.778,57.58 30.589,57.58 31.566,57.58H40.433C41.411,57.58 42.222,57.58 42.883,57.526C43.571,57.47 44.208,57.348 44.809,57.041C45.736,56.568 46.49,55.812 46.963,54.882C47.269,54.279 47.39,53.64 47.446,52.95C47.5,52.287 47.5,51.475 47.5,50.494V44.532C47.5,43.552 47.5,42.739 47.446,42.076C47.39,41.386 47.269,40.747 46.963,40.145C46.536,39.304 45.879,38.606 45.072,38.13C45.03,38.105 44.987,38.081 44.944,38.057V33.969C44.944,29.015 40.94,25 36,25C31.06,25 27.056,29.015 27.056,33.969V38.057C27.013,38.081 26.97,38.105 26.928,38.13ZM43.24,40.101L42.269,40.059C41.782,40.038 41.199,40.038 40.433,40.038H31.567C30.801,40.038 30.218,40.038 29.731,40.059L28.76,40.101L28.385,40.308C27.996,40.522 27.664,40.858 27.446,41.286C27.352,41.472 27.273,41.743 27.229,42.279C27.185,42.818 27.183,43.515 27.183,44.532V50.494C27.183,51.511 27.185,52.208 27.229,52.747C27.273,53.283 27.352,53.555 27.446,53.74C27.675,54.19 28.031,54.539 28.446,54.75C28.597,54.828 28.831,54.902 29.344,54.944C29.867,54.986 30.548,54.988 31.567,54.988H40.434C41.452,54.988 42.133,54.986 42.656,54.944C43.169,54.902 43.403,54.828 43.554,54.75C43.969,54.539 44.325,54.19 44.554,53.74C44.648,53.555 44.727,53.283 44.771,52.747C44.815,52.208 44.817,51.511 44.817,50.494V44.532C44.817,43.515 44.815,42.818 44.771,42.279C44.727,41.743 44.648,41.472 44.554,41.286C44.336,40.858 44.004,40.522 43.615,40.308L43.24,40.101Z"
|
||||
android:fillColor="#020BAC"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:viewBindingIgnore="true"
|
||||
tools:viewBindingIgnore="true"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/prompt_layout"
|
||||
android:layout_width="match_parent"
|
||||
@@ -10,80 +10,40 @@
|
||||
android:orientation="vertical"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<View android:id="@+id/shim"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/login_top_background"
|
||||
android:layout_above="@id/shim">
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:layout_marginTop="20dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/ic_launcher_foreground"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:theme="@style/TextSecure.LightActionBar.DarkText"
|
||||
android:layout_marginTop="20dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_rectangle"
|
||||
android:layout_centerInParent="true"
|
||||
android:padding="20dp"
|
||||
android:elevation="10dp">
|
||||
android:gravity="center">
|
||||
|
||||
<TextView android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:text="@string/prompt_passphrase_activity__signal_is_locked"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="25sp"/>
|
||||
<com.airbnb.lottie.LottieAnimationView
|
||||
android:id="@+id/unlock_view"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
app:lottie_rawRes="@raw/lottie_unlock" />
|
||||
|
||||
<ImageView android:id="@+id/fingerprint_auth_container"
|
||||
android:src="@drawable/ic_fingerprint_white_48dp"
|
||||
android:background="@drawable/circle_tintable"
|
||||
android:backgroundTint="@color/signal_accent_primary"
|
||||
android:padding="20dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="60dp"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<TextView android:id="@+id/lock_screen_auth_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/rounded_rectangle"
|
||||
android:backgroundTint="@color/signal_accent_primary"
|
||||
android:textColor="@color/white"
|
||||
android:gravity="center_horizontal"
|
||||
android:elevation="3dp"
|
||||
android:padding="10dp"
|
||||
android:text="@string/prompt_passphrase_activity__tap_to_unlock"
|
||||
tools:visibility="gone"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/prompt_passphrase_activity__unlock_signal"
|
||||
style="@style/Signal.Text.Headline.Medium" />
|
||||
|
||||
<RelativeLayout android:id="@+id/password_auth_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="60dp"
|
||||
android:layout_marginTop="60dp"
|
||||
tools:visibility="gone">
|
||||
|
||||
<EditText android:id="@+id/passphrase_edit"
|
||||
@@ -147,4 +107,32 @@
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
|
||||
android:id="@+id/learn_more_text"
|
||||
style="@style/Signal.Text.BodyMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toTopOf="@id/lock_screen_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:paddingHorizontal="72dp"
|
||||
android:text="@string/prompt_passphrase_activity__use_your_android_device"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/lock_screen_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Signal.Widget.Button.Medium.Tonal"
|
||||
android:text="@string/prompt_passphrase_activity__unlock"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_marginBottom="80dp"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -4,7 +4,4 @@
|
||||
<item android:title="@string/preferences__submit_debug_log"
|
||||
android:id="@+id/menu_submit_debug_logs"
|
||||
android:icon="@android:drawable/ic_menu_upload" />
|
||||
|
||||
<item android:title="@string/PinRestoreEntryFragment_contact_support"
|
||||
android:id="@+id/menu_contact_support" />
|
||||
</menu>
|
||||
@@ -42,6 +42,13 @@
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
<action
|
||||
android:id="@+id/action_privacySettingsFragment_to_screenLockSettingsFragment"
|
||||
app:destination="@id/screenLockSettingsFragment"
|
||||
app:enterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
|
||||
</fragment>
|
||||
|
||||
@@ -60,6 +67,19 @@
|
||||
android:name="org.thoughtcrime.securesms.components.settings.app.privacy.pnp.PhoneNumberPrivacySettingsFragment"
|
||||
android:label="phone_number_privacy_settings_fragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/screenLockSettingsFragment"
|
||||
android:name="org.thoughtcrime.securesms.components.settings.app.privacy.screenlock.ScreenLockSettingsFragment"
|
||||
android:label="screen_lock_settings_fragment">
|
||||
<action
|
||||
android:id="@+id/action_screenLockSettingsFragment_to_customScreenLockTimerSelectDialog"
|
||||
app:destination="@id/customScreenLockTimerSelectDialog" />
|
||||
</fragment>
|
||||
|
||||
<dialog
|
||||
android:id="@+id/customScreenLockTimerSelectDialog"
|
||||
android:name="org.thoughtcrime.securesms.components.settings.app.privacy.screenlock.CustomScreenLockTimerSelectDialog" />
|
||||
|
||||
<include app:graph="@navigation/app_settings_expire_timer" />
|
||||
|
||||
</navigation>
|
||||
|
||||
1
app/src/main/res/raw-night/lottie_unlock.json
Normal file
@@ -0,0 +1 @@
|
||||
{"v":"4.8.0","meta":{"g":"LottieFiles AE 3.5.8","a":"","k":"","d":"","tc":""},"fr":29.9700012207031,"ip":0,"op":30.0000012219251,"w":720,"h":720,"nm":"Unlock","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0,"y":0},"o":{"x":0.2,"y":0.2},"t":0,"s":[280,199,0],"to":[0,0,0],"ti":[0,0,0]},{"t":9.00000036657752,"s":[280,199,0]}]},"a":{"a":0,"k":[220,195,0]},"s":{"a":1,"k":[{"i":{"x":[0.639,0.639,0.639],"y":[0.12,0.911,1]},"o":{"x":[0.532,0.532,0.532],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.596,0.596,0.596],"y":[0.746,0.231,1]},"o":{"x":[0.274,0.274,0.274],"y":[0.187,-0.228,0]},"t":3,"s":[45.06,120.836,100]},{"i":{"x":[0.578,0.578,0.578],"y":[0.791,0.67,1]},"o":{"x":[0.256,0.256,0.256],"y":[0.307,0.267,0]},"t":4,"s":[-20.373,118.773,100]},{"i":{"x":[0.594,0.594,0.594],"y":[0.484,1,1]},"o":{"x":[0.267,0.267,0.267],"y":[0.768,0.559,0]},"t":6,"s":[-89.031,111.228,100]},{"i":{"x":[0.545,0.545,0.545],"y":[1,1,1]},"o":{"x":[0.227,0.227,0.227],"y":[-2.778,0,0]},"t":9,"s":[-106.75,107,100]},{"t":20.0000008146167,"s":[-100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-57.99,0],[0,-57.99],[0,0],[8.284,0],[0,8.284],[0,0],[41.421,0],[0,-41.421],[0,0],[8.284,0],[0,8.284]],"o":[[0,-57.99],[57.99,0],[0,0],[0,8.284],[-8.284,0],[0,0],[0,-41.421],[-41.421,0],[0,0],[0,8.284],[-8.284,0],[0,0]],"v":[[-105,25],[0,-80],[105,25],[105,65],[90,80],[75,65],[75,25],[0,-50],[-75,25],[-75,65],[-90,80],[-105,65]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.886274509804,0.882352941176,0.898039215686,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[130,125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":94.0000038286985,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[350,360,0]},"a":{"a":0,"k":[180,240,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":9,"s":[115,115,100]},{"t":20.0000008146167,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-35.898,0],[0,0],[0,-35.898],[0,0],[35.898,0],[0,0],[0,35.899]],"o":[[0,-35.898],[0,0],[35.898,0],[0,0],[0,35.899],[0,0],[-35.899,0],[0,0]],"v":[[-140,-55],[-75,-120],[75,-120],[140,-55],[140,55],[75,120],[-75,120],[-140,55]],"c":true}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,-19.33],[0,0],[-19.33,0],[0,0],[0,19.33],[0,0],[19.33,0]],"o":[[-19.33,0],[0,0],[0,19.33],[0,0],[19.33,0],[0,0],[0,-19.33],[0,0]],"v":[[-75,-90],[-110,-55],[-110,55],[-75,90],[75,90],[110,55],[110,-55],[75,-90]],"c":true}},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[0.886274509804,0.882352941176,0.898039215686,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[190,310]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-14.958],[-7.517,-4.813],[0,0],[-8.054,0],[0,8.055],[0,0],[0,9.588],[14.958,0]],"o":[[0,9.588],[0,0],[0,8.055],[8.054,0],[0,0],[7.517,-4.813],[0,-14.958],[-14.957,0]],"v":[[-27.083,-19.792],[-14.583,3.034],[-14.583,32.291],[0,46.875],[14.583,32.291],[14.583,3.034],[27.083,-19.792],[0,-46.875]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.886274509804,0.882352941176,0.898039215686,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[187.083,311.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":900,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false}],"ip":0,"op":900.000036657751,"st":0,"bm":0}],"markers":[]}
|
||||
1
app/src/main/res/raw/lottie_unlock.json
Normal file
@@ -0,0 +1 @@
|
||||
{"v":"4.8.0","meta":{"g":"LottieFiles AE 3.5.8","a":"","k":"","d":"","tc":""},"fr":29.9700012207031,"ip":0,"op":30.0000012219251,"w":720,"h":720,"nm":"Unlock","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0,"y":0},"o":{"x":0.2,"y":0.2},"t":0,"s":[280,199,0],"to":[0,0,0],"ti":[0,0,0]},{"t":9.00000036657752,"s":[280,199,0]}]},"a":{"a":0,"k":[220,195,0]},"s":{"a":1,"k":[{"i":{"x":[0.639,0.639,0.639],"y":[0.12,0.911,1]},"o":{"x":[0.532,0.532,0.532],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.596,0.596,0.596],"y":[0.746,0.231,1]},"o":{"x":[0.274,0.274,0.274],"y":[0.187,-0.228,0]},"t":3,"s":[45.06,120.836,100]},{"i":{"x":[0.578,0.578,0.578],"y":[0.791,0.67,1]},"o":{"x":[0.256,0.256,0.256],"y":[0.307,0.267,0]},"t":4,"s":[-20.373,118.773,100]},{"i":{"x":[0.594,0.594,0.594],"y":[0.484,1,1]},"o":{"x":[0.267,0.267,0.267],"y":[0.768,0.559,0]},"t":6,"s":[-89.031,111.228,100]},{"i":{"x":[0.545,0.545,0.545],"y":[1,1,1]},"o":{"x":[0.227,0.227,0.227],"y":[-2.778,0,0]},"t":9,"s":[-106.75,107,100]},{"t":20.0000008146167,"s":[-100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-57.99,0],[0,-57.99],[0,0],[8.284,0],[0,8.284],[0,0],[41.421,0],[0,-41.421],[0,0],[8.284,0],[0,8.284]],"o":[[0,-57.99],[57.99,0],[0,0],[0,8.284],[-8.284,0],[0,0],[0,-41.421],[-41.421,0],[0,0],[0,8.284],[-8.284,0],[0,0]],"v":[[-105,25],[0,-80],[105,25],[105,65],[90,80],[75,65],[75,25],[0,-50],[-75,25],[-75,65],[-90,80],[-105,65]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.105882352941,0.105882352941,0.113725490196,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[130,125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":94.0000038286985,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[350,360,0]},"a":{"a":0,"k":[180,240,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":9,"s":[115,115,100]},{"t":20.0000008146167,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-35.898,0],[0,0],[0,-35.898],[0,0],[35.898,0],[0,0],[0,35.899]],"o":[[0,-35.898],[0,0],[35.898,0],[0,0],[0,35.899],[0,0],[-35.899,0],[0,0]],"v":[[-140,-55],[-75,-120],[75,-120],[140,-55],[140,55],[75,120],[-75,120],[-140,55]],"c":true}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,-19.33],[0,0],[-19.33,0],[0,0],[0,19.33],[0,0],[19.33,0]],"o":[[-19.33,0],[0,0],[0,19.33],[0,0],[19.33,0],[0,0],[0,-19.33],[0,0]],"v":[[-75,-90],[-110,-55],[-110,55],[-75,90],[75,90],[110,55],[110,-55],[75,-90]],"c":true}},"nm":"Path 2","hd":false},{"ty":"fl","c":{"a":0,"k":[0.105882352941,0.105882352941,0.113725490196,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[190,310]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,-14.958],[-7.517,-4.813],[0,0],[-8.054,0],[0,8.055],[0,0],[0,9.588],[14.958,0]],"o":[[0,9.588],[0,0],[0,8.055],[8.054,0],[0,0],[7.517,-4.813],[0,-14.958],[-14.957,0]],"v":[[-27.083,-19.792],[-14.583,3.034],[-14.583,32.291],[0,46.875],[14.583,32.291],[14.583,3.034],[27.083,-19.792],[0,-46.875]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.105882352941,0.105882352941,0.113725490196,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[187.083,311.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":900,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false}],"ip":0,"op":900.000036657751,"st":0,"bm":0}],"markers":[]}
|
||||
@@ -362,6 +362,26 @@
|
||||
<item>@string/CustomExpireTimerSelectorView__weeks</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="ScreenLockSettingsFragment__labels">
|
||||
<item>@string/ScreenLockSettingsFragment__immediately</item>
|
||||
<item>@string/ScreenLockSettingsFragment__after_1_min</item>
|
||||
<item>@string/ScreenLockSettingsFragment__after_5_min</item>
|
||||
<item>@string/ScreenLockSettingsFragment__after_30_min</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="ScreenLockSettingsFragment__values">
|
||||
<item>0</item>
|
||||
<item>60</item>
|
||||
<item>300</item>
|
||||
<item>1800</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="CustomScreenLockTimerSelectorView__unit_labels">
|
||||
<item>@string/CustomExpireTimerSelectorView__minutes</item>
|
||||
<item>@string/CustomExpireTimerSelectorView__hours</item>
|
||||
<item>@string/CustomExpireTimerSelectorView__days</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="PermissionsSettingsFragment__editor_labels">
|
||||
<item>@string/PermissionsSettingsFragment__only_admins</item>
|
||||
<item>@string/PermissionsSettingsFragment__all_members</item>
|
||||
|
||||
@@ -4278,6 +4278,8 @@
|
||||
<string name="preferences_app_protection__screen_lock">Screen lock</string>
|
||||
<string name="preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint">Lock Signal access with Android screen lock or fingerprint</string>
|
||||
<string name="preferences_app_protection__screen_lock_inactivity_timeout">Screen lock inactivity timeout</string>
|
||||
<!-- Snackbar text shown when trying to enable screen lock but the user does not have a password or biometrics enabled -->
|
||||
<string name="preferences_app_protection__to_use_screen_lock">To use screen lock set a PIN, pattern, or password on this device.</string>
|
||||
<string name="preferences_app_protection__signal_pin">Signal PIN</string>
|
||||
<string name="preferences_app_protection__create_a_pin">Create a PIN</string>
|
||||
<string name="preferences_app_protection__change_your_pin">Change your PIN</string>
|
||||
@@ -4298,8 +4300,41 @@
|
||||
<string name="RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later">You\'ve made too many attempts. Please try again later.</string>
|
||||
<string name="RegistrationActivity_error_connecting_to_service">Error connecting to service</string>
|
||||
<string name="preferences_chats__backups">Backups</string>
|
||||
<string name="prompt_passphrase_activity__signal_is_locked">Signal is locked</string>
|
||||
<string name="prompt_passphrase_activity__tap_to_unlock">TAP TO UNLOCK</string>
|
||||
<!-- Title text shown when Signal is locked and needs to be unlocked -->
|
||||
<string name="prompt_passphrase_activity__unlock_signal">Unlock Signal</string>
|
||||
<!-- Description text explaining how to unlock Signal -->
|
||||
<string name="prompt_passphrase_activity__use_your_android_device">Use your Android device lock settings to unlock Signal.</string>
|
||||
<!-- Text shown in a dialog that further explains how to unlock Signal by using the same unlocking methods that are used to unlock their own device -->
|
||||
<string name="prompt_passphrase_activity__screen_lock_is_on">Screen lock is on and Signal is safely secured with your device lock settings. Unlock Signal the same way as you would normally unlock your phone, this could be with Face Unlock, Fingerprint Unlock, a PIN, password or pattern.</string>
|
||||
<!-- Button in a dialog that will contact Signal support if pressed -->
|
||||
<string name="prompt_passphrase_activity__contact_support">Contact support</string>
|
||||
<!-- Button text to try again after unlocking has previously failed -->
|
||||
<string name="prompt_passphrase_activity__try_again">Try again</string>
|
||||
|
||||
<!-- ScreenLockSettingsFragment -->
|
||||
<!-- Text shown when screen lock is not enabled -->
|
||||
<string name="ScreenLockSettingsFragment__off">Off</string>
|
||||
<!-- Text shown with toggle that when switched on will enable screen lock -->
|
||||
<string name="ScreenLockSettingsFragment__use_screen_lock">Use screen lock</string>
|
||||
<!-- Description of what screen locking will do and how notification content will not be shown when screen is locked -->
|
||||
<string name="ScreenLockSettingsFragment__your_android_device">Your Android device lock settings will be required to unlock Signal when you leave or switch apps. When locked, notification previews will not show message content.</string>
|
||||
<!-- Title text explaining when users should start the screen lock -->
|
||||
<string name="ScreenLockSettingsFragment__start_screen_lock">Start screen lock</string>
|
||||
<!-- Option text explaining that screen lock should start immediately -->
|
||||
<string name="ScreenLockSettingsFragment__immediately">Immediately</string>
|
||||
<!-- Option text explaining that screen lock should start after 1 minute of inactivity -->
|
||||
<string name="ScreenLockSettingsFragment__after_1_min">After 1 minute</string>
|
||||
<!-- Option text explaining that screen lock should start after 5 minutes of inactivity -->
|
||||
<string name="ScreenLockSettingsFragment__after_5_min">After 5 minutes</string>
|
||||
<!-- Option text explaining that screen lock should start after 30 minutes of inactivity -->
|
||||
<string name="ScreenLockSettingsFragment__after_30_min">After 30 minutes</string>
|
||||
<!-- Option text explaining that screen lock should start at a custom time set by the user -->
|
||||
<string name="ScreenLockSettingsFragment__custom_time">Custom time</string>
|
||||
<!-- Title on biometrics prompt explaining that biometrics are needed to turn on screen lock -->
|
||||
<string name="ScreenLockSettingsFragment__use_signal_screen_lock">Use Signal screen lock</string>
|
||||
<!-- Title on biometrics prompt explaining that biometrics are needed to turn off screen lock -->
|
||||
<string name="ScreenLockSettingsFragment__turn_off_signal_lock">Turn off Signal screen lock</string>
|
||||
|
||||
<string name="Recipient_unknown">Unknown</string>
|
||||
<!-- Name to use for a user across the UI when they are unregistered and have no other name available -->
|
||||
<string name="Recipient_deleted_account">Deleted account</string>
|
||||
|
||||